Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2005-01-04 14:48:26


Rambling a little bit today... I set up a little test:

template <class T>
struct A
{
     A(const T&) {}
};

template <class T>
inline
A<T>
make_A(T t)
{
     return A<T>(t);
}

void f() {}

int main()
{
     std::cout << typeid(make_A(0)).name() << '\n';
     const volatile int i = 0;
     std::cout << typeid(make_A(i)).name() << '\n';
     const int a3[3] = {};
     std::cout << typeid(make_A(a3)).name() << '\n';
     int a4[4] = {};
     std::cout << typeid(make_A(a4)).name() << '\n';
     std::cout << typeid(make_A(f)).name() << '\n';
     std::cout << typeid(make_A("narrow")).name() << '\n';
     std::cout << typeid(make_A(L"wide")).name() << '\n';
}

This prints out (on my system):

A<int>
A<int>
A<const int *>
A<int *>
A<void (*)()>
A<const char *>
A<const wchar_t *>

I believe this output, using pass-by-value for make_A, can be
considered "the gold standard" for decay<T>. And here's what you get
if make_A simply uses pass-by-const-ref:

template <class T>
inline
A<T>
make_A(const T& t)
{
     return A<T>(t);
}

A<int>
A<volatile int>
A<int[3]>
A<int[4]>
A<void ()>
A<char[7]>
A<wchar_t[5]>

Using a decay that supports function->function pointer, and one that
strips top level cv-qualifiers off of non-arrays, and non-functions,
and using John's overload strategy:

template <class T>
inline
A<typename decay<T>::type>
make_A(T& t)
{
     return A<typename decay<T>::type>(t);
}

template <class T>
inline
A<typename decay<const T>::type>
make_A(const T& t)
{
     return A<typename decay<const T>::type>(t);
}

I can get the original behavior:

A<int>
A<int>
A<const int *>
A<int *>
A<void (*)()>
A<const char *>
A<const wchar_t *>

As Thorsten points out, remove_reference has no bearing as T can not be
deduced as a reference type in C++03. But fwiw, I can achieve this
output with only one make_A overload using the rvalue reference (if now
decay has remove_reference).

template <class T>
inline
A<typename decay<T>::type>
make_A(T&& t)
{
     return A<typename decay<T>::type>(std::forward<T>(t));
}

The single overload:

template <class T>
inline
A<typename decay<T>::type>
make_A(T& t)
{
     return A<typename decay<T>::type>(t);
}

gets nearly a perfect score, handling const qualified objects
correctly. Its only failing is that it won't bind to rvalues.

The const T& overload by itself doesn't work either because it gets
arrays wrong (turns them into const T*). This exposes a weakness in
the overload strategy: It doesn't scale well. Consider John's
suggested make_pair:

     make_pair( const F& f, const S& s );
     make_pair( F& f, S& s );

This fails with:

     int a4[4] = {};
     std::cout << typeid(std::make_pair(0, a4)).name() << '\n';

std::pair<int, const int *>

intead of:

std::pair<int, int *>

You really need four make_pair overloads to cover everything:

     make_pair( const F& f, const S& s );
     make_pair( const F& f, S& s );
     make_pair( F& f, const S& s );
     make_pair( F& f, S& s );

Now you should get the correct:

std::pair<int, int *>

In contrast, only one make_pair is required when using
rvalue_references:

     make_pair( F&& f, S&& s );

-Howard


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk