Should the type trait be able to handle cases such as std::vector < std::unique_ptr <int> >
and detect that it's not copy constructible?
Here's an example at https://ideone.com/gbcRUa (running g++ 4.8.1)
#include <type_traits>
#include <vector>
#include <iostream>
#include <memory>
int main()
{
// This prints 1, implying that it's copy constructible, when it's clearly not
std::cout << std::is_copy_constructible< std::vector<std::unique_ptr<int> > >::value << std::endl;
return 0;
}
If this is the correct behavior for is_copy_constructible
, is there a way to detect that the copy construction is ill formed? Well, beyond just having it fail to compile.
This is because of a flaw in the design of std::vector
. std::vector
defines copy construction even if it will fail to compile, and relies on users of std::vector
to not invoke the method if it will fail to compile.
The alternative design would be to SFINAE block the invocation of the method if the type contained in the vector
does not have a copy constructor. However, std::vector
was designed before modern SFINAE techniques developed.
It could possibly be retro fitted into a new iteration of C++, as there would be very little code that would break. One cannot say no code would break, because you could have code that relies on the fact that std::is_copy_constructible< std::vector< no_copy_type > >
is std::true_type
, or equivalent expressions, but that is a pretty strange dependency.
On top of the fact that std::vector
is older than the SFINAE techniques that could solve this problem, doing so with SFINAE is pretty messy (as SFINAE is a messy technique). The new concepts-lite proposed for C++1y may make it cleaner, and more tempting to include in a new iteration of the language.
My work around when I have a container that needs to know if the contained object can be safely copied, compared and ordered is to specialize for std::vector
on a custom traits class, and fall back on the value of the custom traits class on the contained type. This is a patchwork solution, and quite intrusive.
template<template<typename>class test, typename T>
struct smart_test : test<T> {};
template<template<typename>class test, typename T, typename A>
struct smart_test<test, std::vector<T,A>> : smart_test<test, T> {};
which gives us:
template<typename T>
using smart_is_copy_constructible = smart_test< std::is_copy_constructible, T >;
and similar for <
and ==
. I can add more specializations when I run into more container-types that should really forward their properties down to their data, or I could write a fancier SFINAE container-test and traits and extract the underlying value-type and dispatch the question to the test on the value-type.
But in my experience, I mostly end up doing these tests on std::vector
.
Note that since c++11 vector has had "participates in overload resolution" rules added to it, which is standards-speak for "do SFINAE" tests.