Consider the following code (question follows below):
#include <iostream>
struct Type0
{
Type0(char* c)
{}
};
struct Type1
{
Type1(int* i=nullptr) : i_(i)
{}
Type1(const Type1& other) = default;
int* i_;
};
template <typename ...>
struct Composable;
template <typename T0, typename ... T>
struct Composable<T0, T...> : T0, Composable<T...>
{
Composable()
{
std::cout << "Default Invoked: " << sizeof...(T) << std::endl;
}
Composable(const Composable& other) = default;
template<typename Arg, typename ... Args>
Composable(Arg&& arg, Args&& ... args) :
T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...)
{
std::cout << "Non-default invoked: " << sizeof...(T) << std::endl;
}
};
template <>
struct Composable<>{};
int main()
{
int i=1;
char c='c';
auto comp = Composable<Type0, Type1>(&c, &i);
std::cout << comp.i_ << std::endl;
}
You can find the live code here. This code has an interesting property: depending on whether you compile it with the --std=C++17 or --std=C++14 option, the behaviour changes (you can try this in my link to the live code: edit the g++ invocation --std parameter in the bottom left).
With --std=c++14, you get the following output:
Non-default invoked: 0
Non-default invoked: 1
Default Invoked: 0
Non-default invoked: 1
0x0
With --std=C++17, you get this instead:
Non-default invoked: 0
Non-default invoked: 1
0x7ffcdf02766c
To me, this difference is baffling. It seems clear that the C++17 version is doing the correct thing, and the C++14 is wrong. The C++14 version is invoking the default constructor of both Composable and (from it) Type1 (this is where the 0x0 final line of output comes from, as Type1 provides this as the default value for its i constructor parameter). However, I don't see any place where the default constructor is supposed to be invoked.
Furthermore, if I comment out the default constructor of Composable altogether, the C++17 version does exactly the same thing as before, whereas the C++14 version now fails to compile, complaining about the lack of a default constructor. If there was any hope of the difference being somehow explained by different optimization behavior, this fact surely kills it (the hope was anyhow small as the observed difference persists at all optimization levels, including 0).
Can anyone explain this difference? Is the C++14 behaviour a bug, or some intended behaviour which I do not understand? If the C++14 behaviour is correct within the rules of C++14, can someone please explain where the default constructor calls are coming from?
Guaranteed copy elision.
This line:
auto comp = Composable<Type0, Type1>(&c, &i);
In C++17, this means exactly the same thing as:
Composable<Type0, Type1> comp(&c, &i);
And if you change to this version, you'll see the same behavior bewteen C++14 and C++17. However, in C++14, that is still a move construction (or, more technically correct as you'll see in a minute, copy-initialization). But in Composable, you don't have an implicitly generated move constructor because you have a user-declared copy constructor. As a result, for the move construction, your "Non-default invoked" constructor template gets invoked (it's a better match than the copy constructor) in the C++14 version:
template<typename Arg, typename ... Args>
Composable(Arg&& arg, Args&& ... args) :
T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...)
Here, Arg is Composable<Type0, Type1> and Args is an empty pack. We delegate to T0 (Type0)'s constructor, forwarding the whole Composable (which works because it inherits from Type0 publicly, so we get the implicitly-generated move constructor there) and to Composable<Type1>'s default constructor (because args is empty).
This constructor template isn't really a proper move constructor - it doesn't end up initializing the Type1 member at all. Instead of moving from the right hand side's Type1::i_ you're invoking Type1::Type1(), the default constructor, which is why you end up with 0.
If you add a proper move constructor:
Composable(Composable&& other) = default;
Then you would again see the same behavior between C++14 and C++17.
Related
Consider the following code snippet:
#include <vector>
#include <ranges>
#include <iostream>
struct A
{
};
struct B
{
B(void *) {};
};
template<class T, class R>
std::vector<T> foo(R &&range)
{
return std::vector<T> {
std::ranges::begin(range),
std::ranges::end(range)
};
}
int main()
{
std::cout << foo<A>(std::views::empty<A>).size() << "\n";
std::cout << foo<B>(std::views::empty<B>).size() << "\n";
}
It correctly prints 0 on the first line and incorrectly prints 2 on the second line on GCC 11.2 (https://godbolt.org/z/s6aoTGbr4) and MSVC 19.30.30705 (Visual Studio 2022 17.0).
Apparently in the second case std::views::empty view produces iterators that causes constructor taking initializer list to be selected.
Changing std::vector<T> { ... } to std::vector<T> ( ... ) fixes this, but I wonder if it is actually a bug in implementation (or even definition) of the empty view?
This is why you should be wary of list-initialization! In particular, this syntax:
std::vector<T>{ ... }
should only (!!) be used when you're specifically trying to invoke the std::initializer_list<T> constructor, and specifically providing elements to the vector and not in any other case. Like this one, where you're not trying to call that constructor.
The issue is that in list-initialization, the std::initializer_list constructor is strongly preferred. If it is at all viable, it is chosen first. In this case, we are trying to construct a vector<B> from two pointers to B (that's the iterator type for empty<B>). A B is constructible from a B* (via the void* constructor), which makes the std::initializer_list<B> constructor a viable candidate, which means it's selected.
As a result, you get a vector<B> with two Bs, each constructed form a null pointer.
For A, there's no such viable constructor, so you fallback to normal initialization, which will select the iterator pair constructor, giving you an empty vector<A>.
The fix here is to use ()s when you initialize, since that's actually what you intend to do.
I have the following code where I'm expecting decltype() to not work on Derived class to get run() base class method return-type, since the base class does not have a default constructor.
class Base
{
public:
int run() { return 1; }
protected:
Base(int){}
};
struct Derived : Base
{
template <typename ...Args>
Derived(Args... args) : Base{args...}
{}
};
int main()
{
decltype(Derived{}.run()) v {10}; // it works. Not expected since
// Derived should not have default constructor
std::cout << "value: " << v << std::endl;
//decltype(Base{}.run()) v1 {10}; // does not work. Expected since
// Base does not have default constructor
//std::cout << "value: " << v1 << std::endl;
}
I'm aware you can use declval<> to get member functions without going through constructors, but my question is why decltype works here. I tried to find in the C++ standard something relevant, but did not find anything. Also tried multiple compilers (gcc 5.2, 7.1 and clang 3.8) and have the same behavior.
Behold the magic of unevaluated contexts... and lying.
Actually trying to do something like:
Derived d;
will be a compile error. It's a compiler error because in the process of evaluating Derived::Derived() we have to invoke Base::Base(), which doesn't exist.
But that's a detail of the implementation of the constructor. In an evaluated context, we certainly need to know that. But in an unevaluated context, we don't need to go so far. If you examine std::is_constructible<Derived>::value, you'd see that is true! This is because you can instantiate that constructor with no arguments - because the implementation of that constructor is outside of the immediate context of that instantiation. This lie - that you can default-construct Derived - allows you to use Derived{} in this context, and the compiler will happily allow you to go on your merry way and see that decltype(Derived{}.run()) is int (which also does not involve actually invoking run, so the body of that function is likewise irrelevant).
If you were honest in the Derived constructor:
template <typename ...Args,
std::enable_if_t<std::is_constructible<Base, Args&...>::value, int> = 0>
Derived(Args&... args) : Base(args...) { }
Then decltype(Derived{}.run()) would fail to compile, because now Derived{} is ill-formed even in an unevaluated context.
It's good to avoid lying to the compiler.
When an expression inside decltype involves a function template, the compiler only looks at the template function's signature to determine whether or not the template could be instantiated if the expression were really in an evaluated context. The actual definition of the function is not used at that point.
(In fact, this is why std::declval can be used inside decltype even though std::declval has no definition at all.)
Your template constructor has the same signature as though simply declared but not yet defined:
template <typename ...Args>
Derived(Args&... args);
While processing the decltype, the compiler just looks at that much information and decides Derived{} is a valid expression, an rvalue of type Derived<>. The : Base{args...} part is part of the template definition and doesn't get used inside decltype.
If you want a compiler error there, you could use something like this to make your constructor more "SFINAE-friendly", which means information about whether or not a specialization of the template is actually valid gets put into the template signature.
template <typename ... Args, typename Enable =
std::enable_if_t<std::is_constructible<Base, Args&...>::value>>
Derived( Args& ... args ) : Base{ args... }
{}
You might also want to modify the constructor to avoid "too perfect forwarding". If you do Derived x; Derived y{x};, the template specialization Derived(Derived&); will be a better match than the implicit Derived(const Derived&);, and you'll end up trying to pass x to Base{x} rather than using the implicit copy constructor of Derived.
I'm playing with this little snippet:
#include <tuple>
struct copy_only
{
copy_only() = default;
copy_only(copy_only&&) = delete;
copy_only(const copy_only&) = default;
};
template <typename ...Ts>
void foo(Ts&& ...xs)
{
auto t = std::make_tuple(std::forward<Ts>(xs)...);
(void) t;
}
int main()
{
foo(copy_only{});
}
It compiles fine with gcc7 and clang3.6, clang3.7, clang3.8 (Wandbox), and clang8.0 (macOS Sierra). It doesn't compile with clang3.9, g++6.2 (macOS Sierra) nor with clang4.0 (Wandbox). All of them complain about deleted move constructor.
It works fine with move-only types. At least on the above compilers available on Wandbox.
Is this code an example of a correct way of generic perfect forwarding into a tuple in c++14?
auto t = std::make_tuple(std::forward<Ts>(xs)...);
This is indeed a correct way of forwarding arguments into a tuple.
The compile errors you get are caused by explicitly declaring copy_only's move constructor as deleted. Normally, if you declared a copy constructor, it'd be omitted and in move contexts the copy constructor would be chosen - as it has since C++98. But because you explicitly declared it, it does participate in the overload resolution and causes the code to be ill-formed if selected.
Here's a helpful chart courtesy of Howard Hinannt:
This can be solved by removing the offending line from the class definition:
struct copy_only
{
copy_only() = default;
//copy_only(copy_only&&) = delete;
copy_only(const copy_only&) = default;
};
Now, as to whether your code should compile or not: as far as I can tell, it should. tuple's move constructor is defined as:
tuple(tuple&& u) = default;
Requires: is_move_constructible<Ti>::value is true for all i.
Since copy_only is not move constructible, it shouldn't be declared and shouldn't participate in overload resolution.
I have a template class that has a template copy constructor. The problem is when I instantiate this class using another instance of this class with the same template type, my template copy constructor is not called. Why doesn't it match?
Here is the code snippet:
#include <iostream>
template <typename T>
class MyTemplateClass
{
public:
MyTemplateClass()
{
std::cout << "default constructor" << std::endl;
}
/*
MyTemplateClass(const MyTemplateClass<T>& other)
{
std::cout << "copy constructor" << std::endl;
}
*/
template <typename U>
MyTemplateClass(const MyTemplateClass<U>& other)
{
std::cout << "template copy constructor" << std::endl;
}
};
int main()
{
MyTemplateClass<int> instance;
MyTemplateClass<int> instance2(instance);
return EXIT_SUCCESS;
}
The output is
default constructor
But if I explicitly write the default copy constructor (by uncommenting it), then the output becomes
default constructor
copy constructor
I really don't get it. I tested it with my local compiler (Clang 500.2.79) and with this one (GCC 4.9.2) and got the same result.
A copy constructor is of the form X(X& ) or (X const&) and will be provided for you by the compiler if you didn't declare one yourself (or a few other conditions which are not relevant here). You didn't, so implicitly we have the following set of candidates:
MyTemplateClass(const MyTemplateClass&);
template <typename U> MyTemplateClass(const MyTemplateClass<U>&);
Both are viable for
MyTemplateClass<int> instance2(instance);
Both take the same exact arguments. The issue isn't that your copy constructor template doesn't match. The issue is that the implicit copy constructor is not a function template, and non-templates are preferred to template specializations when it comes to overload resolution. From [over.match.best], omitting the unrelated bullet points:
Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
— [...]
— F1 is not a function template specialization and F2 is a function template specialization, or, if not that,
— [...]
That's why it calls your implicit (and then, your explicit) copy constructor over your constructor template.
When you do not have a copy constructor in you code, the compiler will implicitly generate it. Therefore when this line is executed:
MyTemplateClass<int> instance2(instance);
A copy constructor is being executed, though obviously not yours. I think that templating has nothing to do with it.
Read more about it here: Implicitly-defined copy constructor
I think REACHUS is right. The compiler is generating a default copy-constructor (as it would with a non-template class too) and preferring this over your template as it's more specialised.
You should make your "normal" copy-constructor private, or better, use the C++11 'deleted' keyword to mark the function as unusable.
However, this doesn't compile. Sorry, I wasn't able to test it at the time.
I just try to understand the behaviour of the following situation:
template <typename T1>
struct A{
template <typename T2>
A(T2 val){
cout<<"sizeof(T1): "<<sizeof(T1)<<" sizeof(T2): "<<sizeof(T2)<<endl;
}
T1 dummyField;
};
so - the class is templated with T1 and the constructor is templated with T2
now - if i write:
A<bool> a = A<bool>(true);
the output is as expected:
sizeof(T1): 1 sizeof(T2): 1
however - if i write:
A<bool> a = A<float>(3.5f);
the output is:
sizeof(T1): 4 sizeof(T2): 4
sizeof(T1): 1 sizeof(T2): 4
why is the constructor called twice with template parameter float?
thanks for satisfying my curiosity
How to avoid copying?
In both cases two constructors are called, however you do not see it in the first case as one of them is the compiler generated one. If you want to avoid copying, you need to use a different syntax, like this:
A<bool> a(true);
A<bool> a(3.5f);
Why (and what) copy constructor is called?
A<bool> a = A<bool>(true);
Here the A (bool val) constructor is used to construct the temporary value, while default compiler generated copy constructor is used to perform the copy of A to A. You are copying the same type, and for same type copy constructor is used. The interesting and not obvious fact here is: Template constructor is never used as a copy constructor, even if it looks like one.
A<bool> a = A<float>(3.5f);
Here A<float>(float val) constructor is used first to construct the temporary value, and then A<bool>( A<float> val) templated constructor is used to perform the copy.
in your first example you make a implicit call to the copy constructor
A<bool>(A<bool> const&)
in your second example this wont work as you have two different types
so the compiler has to use your templated constructor to create a new object
declaring
template <typename T2>
A(A<T2>const& val){
cout<<sizeof(val.dummmyField)<<endl;
}
should make this clear
Because you first create a float-instance of the template class.
This is the A<float>(3.5f)-Part.
Then create the A<bool> by covert the A<float> to a A<bool>. So the constructor is first called for the A<float>-instance. The the copy-constructor of A<bool> is called.