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<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.
Table 49 of the C++11 standard lists what conditions a class has to fulfil for is_copy_constructable<T>::value to be true, and that's unfortunately not much:
is_constructable<T, const T&>::value is true
So if std::vector<T> has a copy constructor it passes the test.
I want to clarify something said by the accepted answer (the one from #Yakk - Adam Nevraumont).
If std::vector doesn't properly delete the copy constructor for non-copyable types, it is not because of a design flaw or a lack of modern SFINAE techniques, it's because those who created it wanted to be able to instantiate a vector of an incomplete type.
Containers can either properly SFINAE their special members, or support incomplete types. There is no good or wrong choice, both of them have their benefits, for more details about that you can check this article which go more in depth: https://quuxplusone.github.io/blog/2020/02/05/vector-is-copyable-except-when-its-not/.
Since C++17 std::vector is required to be instantiable with incomplete types if the allocator allows it (the default one does):
[vector.overview]
An incomplete type T may be used when instantiating vector if the allocator meets the allocator completeness requirements. T shall be complete before any member of the resulting specialization of vector is referenced.
[allocator.requirements.completeness]
If X is an allocator class for type T, X additionally meets the allocator completeness requirements if, whether or not T is a complete type:
X is a complete type, and
all the member types of allocator_traits other than value_type are complete types.
[default.allocator]
All specializations of the default allocator meet the allocator completeness requirements ([allocator.requirements.completeness]).
Related
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<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.
Table 49 of the C++11 standard lists what conditions a class has to fulfil for is_copy_constructable<T>::value to be true, and that's unfortunately not much:
is_constructable<T, const T&>::value is true
So if std::vector<T> has a copy constructor it passes the test.
I want to clarify something said by the accepted answer (the one from #Yakk - Adam Nevraumont).
If std::vector doesn't properly delete the copy constructor for non-copyable types, it is not because of a design flaw or a lack of modern SFINAE techniques, it's because those who created it wanted to be able to instantiate a vector of an incomplete type.
Containers can either properly SFINAE their special members, or support incomplete types. There is no good or wrong choice, both of them have their benefits, for more details about that you can check this article which go more in depth: https://quuxplusone.github.io/blog/2020/02/05/vector-is-copyable-except-when-its-not/.
Since C++17 std::vector is required to be instantiable with incomplete types if the allocator allows it (the default one does):
[vector.overview]
An incomplete type T may be used when instantiating vector if the allocator meets the allocator completeness requirements. T shall be complete before any member of the resulting specialization of vector is referenced.
[allocator.requirements.completeness]
If X is an allocator class for type T, X additionally meets the allocator completeness requirements if, whether or not T is a complete type:
X is a complete type, and
all the member types of allocator_traits other than value_type are complete types.
[default.allocator]
All specializations of the default allocator meet the allocator completeness requirements ([allocator.requirements.completeness]).
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<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.
Table 49 of the C++11 standard lists what conditions a class has to fulfil for is_copy_constructable<T>::value to be true, and that's unfortunately not much:
is_constructable<T, const T&>::value is true
So if std::vector<T> has a copy constructor it passes the test.
I want to clarify something said by the accepted answer (the one from #Yakk - Adam Nevraumont).
If std::vector doesn't properly delete the copy constructor for non-copyable types, it is not because of a design flaw or a lack of modern SFINAE techniques, it's because those who created it wanted to be able to instantiate a vector of an incomplete type.
Containers can either properly SFINAE their special members, or support incomplete types. There is no good or wrong choice, both of them have their benefits, for more details about that you can check this article which go more in depth: https://quuxplusone.github.io/blog/2020/02/05/vector-is-copyable-except-when-its-not/.
Since C++17 std::vector is required to be instantiable with incomplete types if the allocator allows it (the default one does):
[vector.overview]
An incomplete type T may be used when instantiating vector if the allocator meets the allocator completeness requirements. T shall be complete before any member of the resulting specialization of vector is referenced.
[allocator.requirements.completeness]
If X is an allocator class for type T, X additionally meets the allocator completeness requirements if, whether or not T is a complete type:
X is a complete type, and
all the member types of allocator_traits other than value_type are complete types.
[default.allocator]
All specializations of the default allocator meet the allocator completeness requirements ([allocator.requirements.completeness]).
Consider the following:
using vector_type = std::vector<int>;
using const_iterator = typename vector_type::const_iterator;
using const_reverse_iterator = typename vector_type::const_reverse_iterator;
using iterator = typename vector_type::iterator;
using reverse_iterator = typename vector_type::reverse_iterator;
int main()
{
static_assert(!std::is_assignable_v<iterator, const_iterator>); // passes
static_assert(!std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // fails
static_assert(std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // passes
}
I can check that assignment of iterator{} = const_iterator{} is not valid, but not an assignment of reverse_iterator{} = const_reverse_iterator{} with this type trait.
This behavior is consistent across gcc 9.0.0, clang 8.0.0, and MSVC 19.00.23506
This is unfortunate, because the reality is that reverse_iterator{} = const_reverse_iterator{} doesn't actually compile with any of the above-mentioned compilers.
How can I reliably check such an assignment is invalid?
This behavior of the type trait implies that the expression
std::declval<reverse_iterator>() = std::declval<const_reverse_iterator>()
is well formed according to [meta.unary.prop], and this appears consistent with my own attempts at an is_assignable type trait.
This trait passes because the method exists that can be found via overload resolution and it is not deleted.
Actually calling it fails, because the implementation of the method contains code that isn't legal with those two types.
In C++, you cannot test if instantiating a method will result in a compile error, you can only test for the equivalent of overload resolution finding a solution.
The C++ language and standard library originally relied heavily on "well, the method body is only compiled in a template if called, so if the body is invalid the programmer will be told". More modern C++ (both inside and outside the standard library) uses SFINAE and other techniques to make a method "not participate in overload resolution" when its body would not compile.
The constructor of reverse iterator from other reverse iterators is the old style, and hasn't been updated to "not participate in overload resolution" quality.
From n4713, 27.5.1.3.1 [reverse.iter.cons]/3:
template<class U> constexpr reverse_iterator(const reverse_iterator<U>& u);
Effects: Initializes current with u.current.
Notice no mention of "does not participate in overload resolution" or similar words.
The only way to get a trait you want like this would be to
Change (well, fix) the C++ standard
Special case it
I'll leave 1. as an exercise. For 2., you know that reverse iterators are templates over forward iterators.
template<class...Ts>
struct my_trait:std::is_assignable<Ts...> {};
template<class T0, class T1>
struct my_trait<std::reverse_iterator<T0>, std::reverse_iterator<T1>>:
my_trait<T0, T1>
{};
and now my_trait is is_assignable except on reverse iterators, where it instead tests assignability of the contained iterators.
(As extra fun, a reverse reverse iterator will work with this trait).
I once had to do something very similar with std::vector<T>::operator<, which also blindly called T<T and didn't SFINAE disable it if that wasn't legal.
It may also be the case that a C++ standard library implementation can make a constructor which would not compile not participate in overload resolution. This change could break otherwise well formed programs, but only by things as ridiculous as your static assert (which would flip) or things logically equivalent.
It's just a question of constraints. Or lack thereof. Here's a reduced example with a different trait:
struct X {
template <typename T>
X(T v) : i(v) { }
int i;
};
static_assert(is_constructible_v<X, std::string>); // passes
X x("hello"s); // fails
Whenever people talk about being SFINAE-friendly - this is fundamentally what they're referring to. Ensuring that type traits give the correct answer. Here, X claims to be constructible from anything - but really it's not. is_constructible doesn't actually instantiate the entire construction, it just checks the expression validity - it's just a surface-level check. This is the problem that enable_if and later Concepts are intended to solve.
For libstdc++ specifically, we have:
template<typename _Iter>
_GLIBCXX17_CONSTEXPR
reverse_iterator(const reverse_iterator<_Iter>& __x)
: current(__x.base()) { }
There's no constraint on _Iter here, so is_constructible_v<reverse_iterator<T>, reverse_iterator<U>> is true for all pairs T, U, even if it's not actually constructible. The question uses assignable, but in this case, assignment would go through this constructor template, which is why I'm talking about construction.
Note that this is arguably a libstdc++ bug, and probably an oversight. There's even a comment that this should be constrained:
/**
* A %reverse_iterator across other types can be copied if the
* underlying %iterator can be converted to the type of #c current.
*/
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<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.
Table 49 of the C++11 standard lists what conditions a class has to fulfil for is_copy_constructable<T>::value to be true, and that's unfortunately not much:
is_constructable<T, const T&>::value is true
So if std::vector<T> has a copy constructor it passes the test.
I want to clarify something said by the accepted answer (the one from #Yakk - Adam Nevraumont).
If std::vector doesn't properly delete the copy constructor for non-copyable types, it is not because of a design flaw or a lack of modern SFINAE techniques, it's because those who created it wanted to be able to instantiate a vector of an incomplete type.
Containers can either properly SFINAE their special members, or support incomplete types. There is no good or wrong choice, both of them have their benefits, for more details about that you can check this article which go more in depth: https://quuxplusone.github.io/blog/2020/02/05/vector-is-copyable-except-when-its-not/.
Since C++17 std::vector is required to be instantiable with incomplete types if the allocator allows it (the default one does):
[vector.overview]
An incomplete type T may be used when instantiating vector if the allocator meets the allocator completeness requirements. T shall be complete before any member of the resulting specialization of vector is referenced.
[allocator.requirements.completeness]
If X is an allocator class for type T, X additionally meets the allocator completeness requirements if, whether or not T is a complete type:
X is a complete type, and
all the member types of allocator_traits other than value_type are complete types.
[default.allocator]
All specializations of the default allocator meet the allocator completeness requirements ([allocator.requirements.completeness]).
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<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.
Table 49 of the C++11 standard lists what conditions a class has to fulfil for is_copy_constructable<T>::value to be true, and that's unfortunately not much:
is_constructable<T, const T&>::value is true
So if std::vector<T> has a copy constructor it passes the test.
I want to clarify something said by the accepted answer (the one from #Yakk - Adam Nevraumont).
If std::vector doesn't properly delete the copy constructor for non-copyable types, it is not because of a design flaw or a lack of modern SFINAE techniques, it's because those who created it wanted to be able to instantiate a vector of an incomplete type.
Containers can either properly SFINAE their special members, or support incomplete types. There is no good or wrong choice, both of them have their benefits, for more details about that you can check this article which go more in depth: https://quuxplusone.github.io/blog/2020/02/05/vector-is-copyable-except-when-its-not/.
Since C++17 std::vector is required to be instantiable with incomplete types if the allocator allows it (the default one does):
[vector.overview]
An incomplete type T may be used when instantiating vector if the allocator meets the allocator completeness requirements. T shall be complete before any member of the resulting specialization of vector is referenced.
[allocator.requirements.completeness]
If X is an allocator class for type T, X additionally meets the allocator completeness requirements if, whether or not T is a complete type:
X is a complete type, and
all the member types of allocator_traits other than value_type are complete types.
[default.allocator]
All specializations of the default allocator meet the allocator completeness requirements ([allocator.requirements.completeness]).