Why isn't the most appropriate constructor called in this case? - c++

Consider the following class:
class foo {
int data;
public:
template <typename T, typename = enable_if_t<is_constructible<int, T>::value>>
foo(const T& i) : data{ i } { cout << "Value copy ctor" << endl; }
template <typename T, typename = enable_if_t<is_constructible<int, T>::value>>
foo(T&& i) : data{ i } { cout << "Value move ctor" << endl; }
foo(const foo& other) : data{ other.data } { cout << "Copy ctor" << endl; }
foo(foo&& other) : data{ other.data } { cout << "Move ctor" << endl; }
operator int() { cout << "Operator int()" << endl; return data; }
};
Of course it doesn't make much sense to take a single int by any kind of reference, but this is just an example. The data member could be very expensive to copy, hence all the move semantics.
That fancy template basically enables any type from which data can be constructed. So a foo object can be constructed either by copying or moving a value of any type that satisfies this criteria, or simply by copying or moving another object of type foo. Pretty straight forward so far.
The problem occurs when you try to do something like this:
foo obj1(42);
foo obj2(obj1);
What this should do (at lest in my opinion) is to construct the first object by moving the value 42 into it (since it's an rvalue), and then construct the second object by copying the first object. So what this should print out is:
Value move ctor
Copy ctor
But what it actually prints out is:
Value move ctor
Operator int
Value move ctor
The first object gets constructed just fine, no problem there. But instead of calling the copy constructor to construct the second object, the program converts the first object into another type (via the conversion we defined) and then calls another constructor of foo which can move from that type (since it's an rvalue at that point).
I find this very strange, and it's definitely not the behavior I would want from this piece of code. I think it makes more sense to just call the copy constructor upon constructing the second object, as that seems way more trivial given the type of argument I supplied.
Can anyone explain what happens here? Of course I understand that since there's a user-defined conversion to int, this is a perfectly valid path to take, but I cannot make sense of it. Why would the compiler refuse to simply call the constructor which has the exact same argument type as the supplied value? Wouldn't that be the most trivial thing to do, therefore the default behavior? Calling the conversion operator does perform a copy as well, so I don't think that is faster or more optimal than simply calling the copy constructor either.

Your template "move" constructor (with T = foo &) has a parameter of type foo &, which is a better match than your copy constructor, since that only takes const foo &. Your template constructor then fills data by converting i to int, invoking operator int().
The simplest immediate fix could be to use enable_if to restrict your move constructor to move operations: if T is deduced as an lvalue reference type (meaning your T&& i would collapse to an lvalue reference too), force a substitution failure.
template <typename T, typename = enable_if_t<is_constructible<int, T>::value>,
typename = enable_if_t<!is_lvalue_reference<T>::value>>
foo(T&& i) : data( std::move(i) ) { cout << "Value move ctor" << endl; }
Note: since your parameter has a name, inside the constructor, just like all other named objects, it's an lvalue. Given that you want to move from it, you can use std::move.
More generally, you could use perfect forwarding (accepting both lvalues and rvalues), and only remove foo itself as a special exception:
template <typename T, typename = enable_if_t<is_constructible<int, T>::value>
, typename = enable_if_t<!is_same<decay_t<T>, foo>::value>
foo(T&& i) : data( std::forward<T>(i) ) { cout << "Forwarding ctor" << endl; }
This would replace your value copy and value move constructor.
Another note: is_constructible<int, T>::value is a test that tells you whether data(std::forward<T>(i)) would be well-formed. It does not test whether data{std::forward<T>(i)} would be well-formed. A T for which the result is different is long, since the long to int conversion is a narrowing conversion, and narrowing conversions are not allowed in {}.

It happens because of operator int().
because of operator int(), obj1 calls operator int() and casts itself as an int.
Possible solutions:
use static_cast<foo>. this will cast the variable that normally should cast as an int, to a foo.
Anyone please edit this question and fill this. I have no more ideas.

Related

Why is a templatized copy constructor not called? [duplicate]

This question already has answers here:
C++ template copy constructor on template class
(3 answers)
Closed 2 years ago.
Why is it that the following code fails to compile (complains about deleted copy constructor):
struct C {
int i;
C(const C&) = delete;
C(int i) : i(i) {}
template <typename T>
C(const T& value) {
cout << "in templatized constructor!\n";
this->i = value.i * 2;
}
};
int main()
{
C s1{4};
C s2{s1};
cout << s2.i;
}
But when i remove the const from the templatized copy constructor, everything works:
template <typename T>
C(T& value) {
cout << "in templatized constructor!\n";
this->i = value.i * 2;
}
Two questions:
(1) Why isn't the template contructor used as a fall-back when the generated copy constructor is deleted?
(2) Even though "templates can't be copy constructors" - how/why is it that the C(T&); constructor IS being used as a copy constructor (in the second example) ? surely this contradicts the idea that templates can't be copy constructors?
It's just overload resolution. In C s2{s1};, you are looking for a constructor of C that can take a (non-const) C lvalue argument. For template<typename T> C(T const&), the "closest" this constructor can get to matching the argument list is setting T = C, so you get C(C const&) (this solution for T is found by template argument deduction, which we don't need to go into). The copy constructor C(C const&) = delete; can also be called with this argument list. The two implicit conversion sequences involved (C lvalue undergoes identity conversion to bind to C const&), are also exactly the same, so neither overload is immediately "better" than the other than the other. However, the deleted one is not a template, so it's considered to be better anyway. The deleted overload is chosen, causing an error. If you have instead template<typename T> C(T&), then it can get closer to the given argument list, by setting T = C to get C(C&). Now, the implicit conversion sequence where a C lvalue binds to a C& (the case of the template) is better than the implicit conversion sequence where a C lvalue binds to a C const& (the copy constructor), since there are fewer qualifiers to add, so now the template wins and the code compiles.
Addressing the comments, your templated constructor indeed isn't a copy constructor. According to cppreference,
A copy constructor of class T is a non-template constructor...
Also, you may be misunderstanding what = delete; means. C(C const&) = delete; does not mean "there is not an overload with this signature", it means "there is an overload with this signature, and trying to call it is an error". You can't not have a copy constructor as one of the overloads of a class's constructor. deleteing it only makes it an error to use that overload but doesn't stop overload resolution from picking it over another overload.

How can I tell whether I'm forwarding to a copy constructor?

If I'm writing a generic function that forwards arguments to a constructor, is there a way to tell whether that is a copy constructor? Essentially I want to do:
template <typename T, typename... Args>
void CreateTAndDoSomething(Args&&... args) {
// Special case: if this is copy construction, do something different.
if constexpr (...) { ... }
// Otherwise do something else.
...
}
The best I've come up with is checking for sizeof...(args) == 1 and then looking at std::is_same_v<Args..., const T&> || std::is_same_v<Args..., T&>. But I think this misses edge cases like volatile-qualified inputs and things that are implicitly convertible to T.
To be honest I'm not entirely sure this question is well-defined, so feel free to tell me it isn't (and why) as well. If it helps you can assume that the only single-argument constructors for T are T(const T&) and T(T&&).
If I'm right that this isn't well-defined because a copy constructor isn't a Thing, then maybe this can be made more precise by saying "how can I tell whether the expression T(std::forward<Args>(args)...) selects an overload that accepts const T&?
You can use remove_cv_t:
#include <type_traits>
template <typename T, typename... Args>
void CreateTAndDoSomething(Args&&... args) {
// Special case: if this is copy construction, do something different.
if constexpr (sizeof...(Args) == 1 && is_same_v<T&, remove_cv_t<Args...> >) { ... }
// Otherwise do something else.
...
}
This covers all "copy constructors" as defined by the standard, not considering possible default arguments (it is hard to determine whether a given function parameter -- for the function that would be invoked given these parameters -- is defaulted or not).
You had the right idea. Everything that is needed is encoded in the deduced type of Args. Though, if you want to account for all of the cv-qualified cases, there will be a lot to go through. Let's first recognize the different cases that might arise:
Construction (implicit conversions are construction)
Copy construction (commonly T(const T&))
Move construction (commonly T(T&&))
Slicing (calling Base(const Base&) or Base(Base&&) with a Derived)
If weird move or copy constructors are not considered (ones with default parameters), the cases 2-4 could only happen a single argument is passed, everything else is construction. Hence, it is sensible to provide an overload for the single argument case. Trying to do all of these cases in the variadic template is going to be ugly, as you have to use fold expressions or something like std::conjuction/std::disjuction for the if statements to be valid.
We will also find out that recognizing move and copy separately in every single case is impossible. If there is no need to consider copies and moves separately, the solution is easy. But if these cases need to be separated, one can only make a good guess, which should work almost always.
What comes to to slicing, I would probably opt to disable it with a static_assert.
Move and copy combined
Here is the solution using a single argument overload. Let's go over it in detail next.
#include <utility>
#include <type_trait>
#include <iostream>
// Multi-argument case is almost always construction
template<typename T, typename... Args>
void CreateTAndDoSomething(Args&&... args)
{
std::cout << "Constructed" << '\n';
T val(std::forward<Args>(args)...);
}
template<typename T, typename U>
void CreateTAndDoSomething(U&& arg)
{
// U without references and cv-qualifiers
// std::remove_cvref_t in C++20
using StrippedU = std::remove_cv_t<std::remove_reference_t<U>>;
// Extra check is needed because T is a base for itself
static_assert(
std::is_same_v<StrippedU, T> || !std::is_base_of_v<T, StrippedU>,
"Attempting to slice"
);
if constexpr (std::is_same_v<StrippedU, T>)
{
std::cout << "Copied or moved" << '\n';
}
else
{
std::cout << "Constructed" << '\n';
}
T val(std::forward<U>(arg));
}
Here we make use of the fact that U&& (and Args&&) is a forwarding reference. With forwarding references the deduced template argument U is different depending on the value category of the passed arg. Given an arg of type T, the U is deduced such that:
If the arg was an lvalue, the deduced U is T& (cv-qualifiers included).
If the arg was an rvalue, the deduced U is T (cv-qualifiers included).
NOTE: U might deduce to a cv-qualified reference (eg. const Foo&). std::remove_cv only removes toplevel cv-qualifiers, and references can't have toplevel cv-qualifiers. This is why std::remove_cv needs to applied to a non-reference type. If only std::remove_cv was used, the template would fail to recognize cases where U would be const T&, volatile T& or const volatile T&.
Only copy
A copy constructor is called (usually, see note) when U is deduced to T& const T&, volatile T& or const volatile T&. Because we have three cases where the deduced U is a cv-qualified reference and std::remove_cv doesn't work with these, we should just check these cases explicitly:
template<typename T, typename U>
void CreateTAndDoSomething(U&& arg)
{
// U without references and cv-qualifiers
// std::remove_cvref_t in C++20
using StrippedU = std::remove_cv_t<std::remove_reference_t<U>>;
// Extra check is needed because T is a base for itself
static_assert(
std::is_same_v<StrippedU, T> || !std::is_base_of_v<T, StrippedU>,
"Attempting to slice"
);
if constexpr (std::is_same_v<T&, U>
|| std::is_same_v<const T&, U>
|| std::is_same_v<volatile T&, U>
|| std::is_same_v<const volatile T&, U>)
{
std::cout << "Copied" << '\n';
}
else
{
std::cout << "Constructed" << '\n';
}
T val(std::forward<U>(arg));
}
NOTE: This does not recognize copy construction when a move constructor is not available, and the copy constructor with the signature T(const T&) is available. This is because the result of the call to std::forward with an rvalue arg is an xvalue, which can bind to const T&.
Move and copy seperated
DISCLAIMER: this solution only works for the general case (see the pitfalls)
Let's assume that T has a copy constructor with the signature T(const T&) and move constructor with the signature T(T&&), which is really common. const-qualified move constructors do not really make sense, as the moved object needs to be modified almost always.
With this assumption the expression T val(std::forward<U>(arg)); move constructs val, if U was deduced to a non-const T (arg is an non-const rvalue). This gives us two cases:
U is deduced to T
U is deduced to volatile T
By first removing the volatile qualifier from U we can account for both of these cases. When the move construction recognized first, the rest are copy construction:
template<typename T, typename U>
void CreateTAndDoSomething(U&& arg)
{
// U without references and cv-qualifiers
using StrippedU = std::remove_cv_t<std::remove_reference_t<U>>;
// Extra check is needed because T is a base for itself
static_assert(
std::is_same_v<StrippedU, T> || !std::is_base_of_v<T, StrippedU>,
"Attempting to slice"
);
if constexpr (std::is_same_v<std::remove_volatile_t<U>, T>)
{
std::cout << "Moved (usually)" << '\n';
}
else if constexpr (std::is_same_v<StrippedU, T>)
{
std::cout << "Copied (usually)" << '\n';
}
else
{
std::cout << "Constructed" << '\n';
}
T val(std::forward<U>(arg));
}
If you want to play around with the solution, it's available in godbolt. I've also implemented a special class that hopefully helps to visualize the different constructor calls.
Pitfalls of the solution
When the assumption stated earlier is not true, it is impossible to determine exactly whether copy or move constructor is called. There are at least few special cases that cause ambiguity:
If move constructor for T is not available, arg is a rvalue of type T, and the copy constructor has the signature T(const T&):
The xvalue returned by std::forward<U>(arg) will bind to the const T&. This was also discussed in the "only copy" case.
Move recognized, but a copy happens.
If T has a move constructor with the signature T(const T&&) and arg is a const rvalue of type T:
Copy recognized, but a move happens. Similar case with T(const volatile T&&).
I've also decided not to account for the case, when the user explicitly specifies U (T&& and volatile T&& will compile but not recognize properly).

class constructor precedence with a variadic template constructor for a value wrapper

Today I've discovered that I don't understand the C++ constructor precedence rules.
Please, see the following template struct wrapper
template <typename T>
struct wrapper
{
T value;
wrapper (T const & v0) : value{v0}
{ std::cout << "value copy constructor" << std::endl; }
wrapper (T && v0) : value{std::move(v0)}
{ std::cout << "value move constructor" << std::endl; }
template <typename ... As>
wrapper (As && ... as) : value(std::forward<As>(as)...)
{ std::cout << "emplace constructor" << std::endl; }
wrapper (wrapper const & w0) : value{w0.value}
{ std::cout << "copy constructor" << std::endl; }
wrapper (wrapper && w0) : value{std::move(w0.value)}
{ std::cout << "move constructor" << std::endl; }
};
It's a simple template value wrapper with copy constructor (wrapper const &), a move constructor (wrapper && w0), a sort of value copy constructor (T const & v0), a sort of move constructor (T && v0) and a sort of template construct-in-place-the-value constructor (As && ... as, following the example of emplace methods for STL containers).
My intention was to use the copy or moving constructor calling with a wrapper, the value copy or move constructor passing a T object and the template emplace constructor calling with a list of values able to construct an object of type T.
But I don't obtain what I expected.
From the following code
std::string s0 {"a"};
wrapper<std::string> w0{s0}; // emplace constructor (?)
wrapper<std::string> w1{std::move(s0)}; // value move constructor
wrapper<std::string> w2{1u, 'b'}; // emplace constructor
//wrapper<std::string> w3{w0}; // compilation error (?)
wrapper<std::string> w4{std::move(w0)}; // move constructor
The w1, w2 and w4 values are constructed with value move constructor, emplace constructor and move constructor (respectively) as expected.
But w0 is constructed with emplace constructor (I was expecting value copy constructor) and w3 isn't constructed at all (compilation error) because the emplace constructor is preferred but ins't a std::string constructor that accept a wrapper<std::string> value.
First question: what am I doing wrong?
I suppose that the w0 problem it's because s0 isn't a const value, so the T const & isn't an exact match.
Indeed, if I write
std::string const s1 {"a"};
wrapper<std::string> w0{s1};
I get the value copy constructor called
Second question: what I have to do to obtain what I want?
So what I have to do to make the value copy constructor (T const &) to get the precedence over the emplace constructor (As && ...) also with not constant T values and, mostly, what I have to do to get the copy constructor (wrapper const &) take the precedence constructing w3?
There is no such thing as "constructor precedence rules," there's nothing special about constructors in terms of precedence.
The two problem cases have the same underlying rule explaining them:
wrapper<std::string> w0{s0}; // emplace constructor (?)
wrapper<std::string> w3{w0}; // compilation error (?)
For w0, we have two candidates: the value copy constructor (which takes a std::string const&) and the emplace constructor (which takes a std::string&). The latter is a better match because its reference is less cv-qualified than the value copy constructor's reference (specifically [over.ics.rank]/3). A shorter version of this is:
template <typename T> void foo(T&&); // #1
void foo(int const&); // #2
int i;
foo(i); // calls #1
Similarly, for w3, we have two candidates: the emplace constructor (which takes a wrapper&) and the copy constructor (which takes a wrapper const&). Again, because of the same rule, the emplace constructor is preferred. This leads to a compile error because value isn't actually constructible from a wrapper<std::string>.
This is why you have to be careful with forwarding references and constrain your function templates! This is Item 26 ("Avoid overloading on universal references") and Item 27 ("Familiarize yourself with alternatives to overloading on universal references") in Effective Modern C++. Bare minimum would be:
template <typename... As,
std::enable_if_t<std::is_constructible<T, As...>::value, int> = 0>
wrapper(As&&...);
This allows w3 because now there is only one candidate. The fact that w0 emplaces instead of copies shouldn't matter, the end result is the same. Really, the value copy constructor doesn't really accomplish anything anyway - you should just remove it.
I would recommend this set of constructors:
wrapper() = default;
wrapper(wrapper const&) = default;
wrapper(wrapper&&) = default;
// if you really want emplace, this way
template <typename A=T, typename... Args,
std::enable_if_t<
std::is_constructible<T, A, As...>::value &&
!std::is_same<std::decay_t<A>, wrapper>::value
, int> = 0>
wrapper(A&& a0, Args&&... args)
: value(std::forward<A>(a0), std::forward<Args>(args)...)
{ }
// otherwise, just take the sink
wrapper(T v)
: value(std::move(v))
{ }
That gets the job done with minimal fuss and confusion. Note that the emplace and sink constructors are mutually exclusive, use exactly one of them.
As OP suggested, putting my comment as an answer with some elaboration.
Due to the way overload resolution is performed and types are matched, a variadic forward-reference type of constructor will often be selected as a best match. This would happen because all const qualification will be resolved correctly and form a perfect match - for example, when binding a const reference to a non-const lvalue and such - like in your example.
One way to deal with them would be to disable (through various sfinae methods at our disposal) variadic constructor when argument list matches (albeit imperfectly) to any of other available constructors, but this is very tedious, and requires ongoing support whenever extra constructors are added.
I personally prefer a tag-based approach and use a tag type as a first argument to variadic constructor. While any tag structure would work, I tend to (lazily) steal a type from C++17 - std::in_place. The code now becomes:
template<class... ARGS>
Constructor(std::in_place_t, ARGS&&... args)
And than called as
Constructor ctr(std::in_place, /* arguments */);
Since in my experience in the calling place the nature of constructor is always known - i.e. you will always know if you intend to call forward-reference accepting constructor or not - this solution works well for me.
As said in the comment, the problem is that the variadic template constructor
takes argument by forwarding reference, so it is a better match for non const lvalue copy or const rvalue copy.
There are many way to disable it, one efficient way is to always use a tag as in_place_t as proposed by SergeyA in its answer. The other is to disable the template constructor when it matches the signature of a copy constructor as it is proposed in the famous Effective C++ books.
In this case, I prefer to declare all possible signature for copy/move constructors (and also copy/move assignment). This way, whatever new constructor I add to the class, I will not have to think about avoiding copy construction, it is short 2 line of code, easy to read and it does not pollute the interface of other constructors:
template <typename T>
struct wrapper
{
//...
wrapper (wrapper& w0) : wrapper(as_const(w0)){}
wrapper (const wrapper && w0) : wrapper(w0){}
};
NB: this solution should not be used if one plan to use it as a volatile type, or if all the following condition are fullfilled:
the object size is smaller than 16bytes (or 8 byte for MSVC ABI),
all member suboject are trivially copyable,
this wrapper is going to be passed to functions where special care is taken for the case where the argument is of a trivially copyable type and its size is lower than the previous threshold because in this case, the argument can be passed in a register (or two) by passing the argument by value!
If all these requirement are fulfilled, then you may think about implementing less maintainable (error prone -> next time one will modify the code) or client interface polluting solution!

Which type trait would indicate that type is memcpy assignable? (tuple, pair)

I would like to know what type introspection I can do to detect types that assignable by simply raw memory copy?
For example, as far I understand, built-in types tuples of built-in types and tuple of such tuples, would fall in this category.
The motivation is that I want to transport raw bytes if possible.
T t1(...); // not necessarely default constructible
T t2(...);
t1 = t2; // should be equivalent to std::memcpy(&t1, &t2, sizeof(T));
// t1 is now an (independent) copy of the value of t2, for example each can go out of scope independently
What type_trait or combination of type_traits could tell at compile time if assignment can be (in principle) replaced by memcpy?
I tried what would work for the types I would guess should fullfil this condition and to my surprise the only one that fit the behavior is not std::is_trivially_assignable but std::trivially_destructible.
It makes sense to some level, but I am confused why some other options do not work with the expected cases.
I understand that there may not be a bullet proof method because one can always write a class that effectively is memcopyable, that cannot be "detected" as memcopyable, but I am looking for one that works for the simple intuitive cases.
#include<type_traits>
template<class T> using trait =
std::is_trivially_destructible
// std::is_trivial
// std::is_trivially_copy_assignable
// std::is_trivially_copyable // // std::tuple<double, double> is not trivially copyable!!!
// std::is_trivially_default_constructible
// std::is_trivially_default_constructible
// std::is_trivially_constructible
// std::is_pod // std::tuple<double, double> is not pod!!!
// std::is_standard_layout
// std::is_aggregate
// std::has_unique_object_representations
<T>
;
int main(){
static_assert((trait<double>{}), "");
static_assert((trait<std::tuple<double, double>>{}), "");
static_assert((not trait<std::tuple<double, std::vector<double>>>{}), "");
static_assert((not trait<std::vector<double>>{}), "");
}
Of course my conviction that tuple should be memcopyable is not based on the standard but based on common sense and practice. That is, because this is generally ok:
std::tuple<double, std::tuple<char, int> > t1 = {5.1, {'c', 8}};
std::tuple<double, std::tuple<char, int> > t2;
t2 = t1;
std::tuple<double, std::tuple<char, int> > t3;
std::memcpy(&t3, &t1, sizeof(t1));
assert(t3 == t2);
As a proof of principle, I implemented this. I added a couple of conditions related to the size to avoid some possible misleading specialization of std::tuple.
template<class T>
struct is_memcopyable
: std::integral_constant<bool, std::is_trivially_copyable<T>{}>{};
template<class T, class... Ts>
struct is_memcopyable<std::tuple<T, Ts...>> :
std::integral_constant<bool,
is_memcopyable<T>{} and is_memcopyable<std::tuple<Ts...>>{}
>
{};
template<class T1, class T2>
struct is_memcopyable<std::pair<T1, T2>> :
std::integral_constant<bool,
is_memcopyable<T1>{} and is_memcopyable<T2>{}
>
{};
This is a very limited workaround because a class like:
struct A{ std::tuple<double, double> t; };
will still unfortunately be reported as non trivially copyable and non memcopyable.
The correct test is in fact std::is_trivially_copyable, which allows use of memcpy for both making a new object and modifying an existing one.
Although you may be surprised that these return false for types where your intuition tells you that memcpy ought to be ok, they are not lying; the Standard indeed makes memcpy undefined behavior in these cases.
In the particular case of std::pair, we can get some insight into what goes wrong:
int main()
{
typedef std::pair<double,double> P;
std::cout << "\nTC: " << std::is_trivially_copyable<P>::value;
std::cout << "\nTCC: " << std::is_trivially_copy_constructible<P>::value;
std::cout << "\nTCv: " << std::is_trivially_constructible<P, const P&>::value;
std::cout << "\n CC: " << std::is_copy_constructible<P>::value;
std::cout << "\n MC: " << std::is_move_constructible<P>::value;
std::cout << "\nTCA: " << std::is_trivially_copy_assignable<P>::value;
std::cout << "\nTCvA:" << std::is_trivially_assignable<P, const P&>::value;
std::cout << "\n CA: " << std::is_copy_assignable<P>::value;
std::cout << "\n MA: " << std::is_move_assignable<P>::value;
std::cout << "\nTD: " << std::is_trivially_destructible<P>::value;
}
TC: 0
TCC: 1
TCv: 1
CC: 1
MC: 1
TCA: 0
TCvA:0
CA: 1
MA: 1
TD: 1
Evidently it isn't trivially copy assignable.1
The pair assignment operator is user-defined, so not trivial.
1I think that clang, gcc, and msvc are all wrong here, actually, but if it satisfied std::_is_trivially_copy_assignable it wouldn't help, because TriviallyCopyable requires that the copy constructor, if not deleted, is trivial, and not the TriviallyCopyAssignable trait. Yeah, they're different.
A copy/move assignment operator for class X is trivial if it is not user-provided and...
vs
is_assignable_v<T, const T&> is true and the assignment, as defined by
is_assignable, is known to call no operation that is not trivial.
The operations called by pair<double, double>'s copy assignment operator are the assignments of individual doubles, which are trivial.
Unfortunately, the definition of trivially copyable relies on the first, which pair fails.
A trivially copyable class is a class:
where each copy constructor, move constructor, copy assignment operator, and move assignment operator is either deleted or trivial,
that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and
that has a trivial, non-deleted destructor.
This is only a partial answer to your question:
Type traits don't necessarily mean what their name says literally.
Specifically, let's take std::is_trivially_copyable. You were - rightly - surprised that a tuple of two double's is not trivially copyable. How could that be?!
Well, the trait definition says:
If T is a TriviallyCopyable type, provides the member constant value equal true. For any other type, value is false.
and the TriviallyCopyable concept has the following requirement in its definition:
Every copy constructor is trivial or deleted
Every move constructor is trivial or deleted
Every copy assignment operator is trivial or deleted
Every move assignment operator is trivial or deleted
At least one copy constructor, move constructor, copy assignment operator, or move assignment operator is non-deleted
Trivial non-deleted destructor
Not quite what you would expect, right?
With all in mind, it's not necessarily the case that any of the standard library traits would combine to fit the exact requirements of "constructible by memcpy()'ing".
To try and answer your question: std::memcpy() does not have any direct requirements but it does have these stipulations:
Copies count bytes from the object pointed to by src to the object pointed to by dest. Both objects are reinterpreted as arrays of unsigned char.
If the objects overlap, the behavior is undefined.
If either dest or src is a null pointer, the behavior is undefined, even if count is zero.
If the objects are not TriviallyCopyable, the behavior of memcpy is not specified and may be undefined.
Now to have the qualifications that an object is Trivially Copyable the following conditions or requirements must be met:
Every copy constructor is trivial or deleted
Every move constructor is trivial or deleted
Every copy assignment operator is trivial or deleted
Every move assignment operator is trivial or deleted
at least one copy constructor, move constructor, copy assignment operator, or move assignment operator is non-deleted
Trivial non-deleted destructor
This implies that the class has no virtual functions or virtual base classes.
Scalar types and arrays of TriviallyCopyable objects are TriviallyCopyable as well, as well as the const-qualified (but not volatile-qualified) versions of such types.
Which leads us to std::is_trivially_copyable
If T is a TriviallyCopyable type, provides the member constant value equal true. For any other type, value is false.
The only trivially copyable types are scalar types, trivially copyable classes, and arrays of such types/classes (possibly const-qualified, but not volatile-qualified).
The behavior is undefined if std::remove_all_extents_t is an incomplete type and not (possibly cv-qualified) void.
with this nice feature since c++17:
Helper variable template
template< class T >
inline constexpr bool is_trivially_copyable_v = is_trivially_copyable<T>::value;
And you would like to try and use a type_trait to use std::tuple<> with std::memcpy().
But we need to ask ourselves if std::tuple is Trivially Copyable and why?
We can see the answer to that here: Stack-Q/A: std::tuple Trivially Copyable? and according to that answer; it is not because the standard does not require the copy/move assignment operators to be trivial.
So the answer that I would think that is valid would be this: No std::tuple is not Trivially Copyable but std::memcpy() doesn't require it to be but only states that if it isn't; it is UB. So can you use std::tuple with std::memcpy? I think so, but is it safe? That can vary and can produce UB.
So what can we do from here? Take a risk? Maybe. I found something else that is related but have not found anything out about it regarding if it is Trivially Copyable. It is not a type_trait, but it is something that might be able to be used in conjunction with std::tuple & std::memcpy and that is std::tuple_element. You might be able to use this to do the memcpy, but I'm not fully sure about it. I have searched to find out more about std::tuple_element to see if it is Trivially Copyable but haven't found much so all I can do is a test to see what Visual Studio 2017 says:
template<class... Args>
struct type_list {
template<std::size_t N>
using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
};
int main() {
std::cout << std::boolalpha;
std::cout << std::is_trivially_copyable<type_list<int, float, float>>::value << '\n';
std::cout << std::is_trivially_copyable<std::tuple<int, float, float>>::value << '\n';
_getch(); // used to stop visual studio debugger from closing.
return 0;
}
Output:
true
false
So it appears if we wrap std::tuple_element in a struct it is Trivially Copyable. Now the question is how do you integrate this with your std::tuple data sets to use them with std::memcpy() to be type safe. Not sure if we can since std::tuple_element will return the types of the elements within a tuple.
If we even tried to wrap a tuple in a struct as such:
template<class... Args>
struct wrapper {
std::tuple<Args...> t;
};
And we can check it by:
{
std::cout << std::is_trivially_copyable< wrapper<int, float, float> >::value << std::endl;
}
It is still false. However we have seen were std::tuple was already used in the first struct and the struct returned true. This may be of some help to you, to ensure you can safely use std::memcpy, but I can not guarantee it. It is just that the compiler seems to agree with it. So this might be the closest thing to a type_trait that might work.
NOTE: - All the references about memcpy, Trivially Copyable concepts, is_trivially_copyable, std::tuple & std::tuple_element were taken from cppreference and their relevant pages.

std::tuple with generic types like boost::any

Dear fellow programmers,
the code below gives me some headaches. It tries to add a 'generic' object (=object that can be constructed from anything) to a tuple and then copy that tuple.
#include <tuple>
#include <iostream>
#include <typeinfo>
struct anything
{
anything()
{}
anything(const anything&)
{
std::cout << "Called copy c'tor" << std::endl;
}
template<class T>
anything(T arg)
{
std::cout << "Called c'tor with with argument of type " << typeid(arg).name() << std::endl;
// new T(arg); // causes stack overflow
}
};
int main()
{
std::tuple<anything> t;
//
std::cout << "Copy constructing t2, expecting copy c'tor to be called." << std::endl;
std::tuple<anything> t2(t);
return 0;
}
With VS 2015 Update 2 it doesn't even compile, the line
std::tuple<anything> t2(t);
triggers a compiler error deep in tuple.h.
With gcc 5.3.1 it compiles, but the output is not what I'd expect:
Copy constructing t2, expecting copy c'tor to be called.
Called copy c'tor
Called c'tor with with argument of type St5tupleIJ8anythingEE
What I don't understand is the last line, i.e. why the templated constructor gets called with std::tuple as argument?
This is actually a real world problem. In my application I use a boost::signal of signature
typedef boost::type_erasure::any
<boost::mpl::vector<
boost::type_erasure::typeid_<>,
boost::type_erasure::copy_constructible<>,
boost::type_erasure::less_than_comparable<>,
boost::type_erasure::equality_comparable<>,
boost::type_erasure::relaxed
>> KeyType;
boost::signals2::signal<void(const KeyType&)>
Boost signals internally wraps the argument in a tuple before calling the slot function with it, which eventually results in a stack overflow, because the tuple c'tor calls the any c'tor with tuple as argument and the any c'tor then calls the tuple c'tor with 'any of tuple' and so on and on and on...
Let's go through overload resolution for:
std::tuple<anything> t2(t);
We have three viable constructors at our disposal:
explicit tuple( const Types&... args ); // (2) with Types = [anything]
template< class... UTypes >
explicit tuple( UTypes&&... args ); // (3) with UTypes = [std::tuple<anything>&]
tuple( const tuple& other ) = default; // (8)
Which have these argument lists:
tuple(const anything& ); // (2)
tuple(std::tuple<anything>& ); // (3)
tuple(std::tuple<anything> const& ); // (8)
(2) involves a user-defined conversion whereas (3) and (8) are exact matches. When it comes to reference bindings:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence
S2 if [...] S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same
type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers
is more cv-qualified than the type to which the reference initialized by S1 refers.
So (3) is preferred - since it's less cv-qualified than (8). That constructor calls anything(std::tuple<anything> ).
As far as solutions, what you need is for (3) to not be considered in this case - we need to make it not a viable option (since (8) is already preferred to (2)). Currently, the easiest thing is just to make your constructor explicit:
template<class T>
explicit anything(T arg) { ... }
this works since (3) is specified in terms of is_convertible<>, which will return false on explicit conversions. However, that's currently considered a defect and will likely be changed in the future - since after all, we are explicitly constructing every aspect here so the explicit constructors should still be considered!
Once that happens, you're kind of out of luck as far as direct copy construction goes. You'd have to do something like disable your anything constructor for tuple? That seems... not great. But in that case, marking that constructor explicit would still work for copy-initialization:
std::tuple<anything> t2 = t;
which works now even without marking the anything constructor explicit, due to the same aforementioned defect.
If you look at the implementation of the tuple you'll notice that
_Tuple_val<_This> _Myfirst; // the stored element
...
template<class _This2,
class... _Rest2,
class = typename _Tuple_enable<tuple<_This2, _Rest2...>, _Myt>::type>
explicit tuple(_This2&& _This_arg, _Rest2&&... _Rest_arg)
: _Mybase(_STD forward<_Rest2>(_Rest_arg)...),
_Myfirst(_STD forward<_This2>(_This_arg))
{ // construct from one or more moved elements
}
The constructor of the tuple passes first argument to the the constructor of the first element of tuple. As variable t has type std::tuple<anything> compiler finds the best match for constructing anything with t - a template constructor.
To copy a tuple you simply need to write
std::tuple<anything> t2 = t;
UPD.
According to standard std::tuple provides following constructors:
template <class... UTypes>
explicit constexpr tuple(const Types&...);
template <class... UTypes>
constexpr tuple(const tuple<UTypes...>&);
And apparently your template constructor is a better match than copy constructor of the tuple.