|
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