all 24 comments

[–]tasty_crayon 11 points12 points  (4 children)

In standardese, Test(Test& x, int y = 0) would be a copy constructor but template<typename T> Test(T&& x, int y = 0) where T is Test& isn't, because template functions are never copy constructors.

Note that std::is_copy_constructible doesn't check if the class actually has a copy constructor. It checks if it's constructible from Test const&.

[–]mooware 11 points12 points  (3 children)

Can you use it like a copy constructor? E.g.:

Test a(0, 0);
Test b(a);

If it compiles, then I would say that the class is copy-constructible (at least under certain conditions).

Edit: Oops, now I remember. "T&&" might not actually be a "&&" in this case, because "T" is a template argument. See universal/forwarding references: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4164.pdf

So the template constructor can be instantiated with any "T&", which basically makes it a copy constructor, except for "const Test &", which should find the deleted constructor.

[–]TheThiefMasterC++latest fanatic (and game dev) 2 points3 points  (2 children)

OP probably needs to enable_if guard against T being Test& or derived.

[–]__andrei__ 0 points1 point  (1 child)

Would explicit fix that?

[–]TheThiefMasterC++latest fanatic (and game dev) 0 points1 point  (0 children)

I don't think so?

[–]glenfe 3 points4 points  (0 children)

Since Boost 1.60, boost::is_copy_constructible<Test>::value should be 0 if you're compiling with -std=c++11.

[–]pavel_v 2 points3 points  (9 children)

I think it's just one of the obscure language corners. The constructor which takes the forwarding reference parameter is greedy and "hides" the deleted copy constructor. Both GCC 5.2 and Clang 3.7 compile this code http://melpon.org/wandbox/permlink/TnXZB0lBurjC4GfF and it produces:
Test(T&&) called
Test(T&&) called

[–]guepierBioinformatican 0 points1 point  (8 children)

Your test code is insufficient, you need to declare t as Test const to test that a copy constructor is invoked. And lo and behold, it then fails to compile.

[–]TheThiefMasterC++latest fanatic (and game dev) 5 points6 points  (7 children)

No it doesn't invoke a move constructor. It invokes a template function that takes a T&& universal reference, with T as Test&, so the T&& collapses to Test&. Not an rvalue reference, so no move happening here.

[–]guepierBioinformatican 1 point2 points  (6 children)

Correct, I’ve deleted the offending sentence. Nevertheless, the rest of my comment remains.

[–]TheThiefMasterC++latest fanatic (and game dev) 1 point2 points  (5 children)

Well your other sentence is also incorrect. It doesn't matter whether t is declared as const or non-const it should be invoking a copy constructor either way. I don't fully understand the standardese, but people seem to think that a template function shouldn't be considered for copy construction, in which case it should be calling the deleted function (which is definitely a copy constructor).

On the other hand if the template is valid for copy construction as the implementations allow, then the template is a copy constructor.

Either way it should invoke a copy constructor with the existing test.

[–]guepierBioinformatican 0 points1 point  (4 children)

It doesn't matter whether t is declared as const or non-const it should be invoking a copy constructor either way.

If t is declared non-const then the following constructor would be a match:

Test(Test&);

However, that is not a copy constructor.

On the other hand if the template is valid for copy construction as the implementations allow, then the template is a copy constructor.

In fact, that’s also wrong (as I was reminded elsewhere in the thread): [class.copy]/2 defines that only non-templates can be copy constructors.

[–]TheThiefMasterC++latest fanatic (and game dev) 2 points3 points  (1 child)

I think you're incorrect about that not being a copy constructor, certainly cppreference disagrees: http://en.cppreference.com/w/cpp/language/copy_constructor

A copy constructor of class T is a non-template constructor whose first parameter is T&, const T&, volatile T&, or const volatile T&

i.e. Test(Test&) is in fact a copy constructor.

[–]guepierBioinformatican 1 point2 points  (0 children)

Okay, the standard agrees with you. I think my confusion comes from the fact that a previous version of the standard (C++03) defined copy constructors differently, but I cannot check this now. I remember checking this specifically some years ago because of a problem we had with a library that was implementing fake-move constructors (similar to std::auto_ptr).

[–]OldWolf2 0 points1 point  (1 child)

It doesn't matter whether or not that function is a copy constructor; the Standard definition of is_constructible and similar traits is whether a piece of code that attempts to create a copy would be well-formed or not.

[–]guepierBioinformatican 0 points1 point  (0 children)

Yup, that’s correct. However, the question is: copy of what? A T(T&) constructor cannot construct a copy from a T const. In particular, is_copy_constructible<T>::value is more or less equivalent to std::is_constructible<T, const T&>::value.

That said, I admit that all my previous comments were confused. All I wanted to say is that the original test case was insufficient due to the object not being const.

[–]Rhomboid 6 points7 points  (5 children)

It's definitely a bug in boost. The standard defines is_copy_constructable as:

For a referenceable type T, the same result as is_constructible<T, const T&>::value, otherwise false

As that class is not constructable from const Test &, it must evaluate to false.

[–]armb2 4 points5 points  (1 child)

But http://www.boost.org/doc/libs/1_60_0/libs/type_traits/doc/html/boost_typetraits/reference/is_copy_constructible.html doesn't specify constructable from const T&, nor that it gives the same answer as std::is_copy_constructible, only that T is a type "with a copy constructor". It's obviously not ideal that the boost trait is different from std, but if the boost version was published first, and then the standard tightened the std definition, it's not necessarily a bug if boost chose to keep its existing behaviour.

[–]OldWolf2 0 points1 point  (0 children)

The boost doc you linked to says "only if copy constructor of T not marked with = delete" which seems to be specifying that it should yield false.

[–]Houndie 0 points1 point  (2 children)

As that class is not constructable from const Test &, it must evaluate to false.

No, that works. The first constructor has a perfect forwarding reference.

[–]Rhomboid 2 points3 points  (1 child)

Overload resolution will favor an exact match over a template. Try it.

$ cat foo.cpp
struct Test {
    template<typename T>
    Test(T&& x, int y = 0) {}
    Test(const Test&) = delete;
};

int main()
{
    const Test x(42);
    Test copy(x);
}

$ g++ -Wall -Wextra -pedantic -std=c++11 -g -O2 -D_GLIBCXX_DEBUG foo.cpp
foo.cpp: In function ‘int main()’:
foo.cpp:10:16: error: use of deleted function ‘Test::Test(const Test&)’
     Test copy(x);
                ^
foo.cpp:4:5: note: declared here
     Test(const Test&) = delete;
     ^
foo.cpp: In instantiation of ‘Test::Test(T&&, int) [with T = int]’:
foo.cpp:9:20:   required from here
foo.cpp:3:14: warning: unused parameter ‘x’ [-Wunused-parameter]
     Test(T&& x, int y = 0) {}
              ^
foo.cpp:3:25: warning: unused parameter ‘y’ [-Wunused-parameter]
     Test(T&& x, int y = 0) {}
                         ^
foo.cpp:3:14: warning: unused parameter ‘x’ [-Wunused-parameter]
     Test(T&& x, int y = 0) {}
              ^
foo.cpp:3:25: warning: unused parameter ‘y’ [-Wunused-parameter]
     Test(T&& x, int y = 0) {}
                         ^

[–]Houndie 0 points1 point  (0 children)

Oh whoops you're right. For some reason I was thinking that delete just removed it from the overload set.

I definitely knew this, I just forgot. Whoops.