Consider a class which inherits from a std container with a template constructor which calls the underlying constructor of the container. This template constructor works for the simple copy and move constructor but not for the initializer_list ctor.
template<typename container_T>
class test : public container_T {
public:
using container_type = container_T;
test() {}
// templated constructor
template<typename T>
test(T t)
: container_T(t) {}
// without this it won't compile
test(std::initializer_list<typename container_T::value_type> l)
: container_T(l) {}
};
int main() {
test<std::deque<int>> vdi1;
test<std::deque<int>> vdi2({1,2,3,4,5,6,7,8,9});
std::cout << "vdi2 before:" << std::endl;
for(auto it : vdi2)
std::cout << it << std::endl;
test<std::deque<int>> vdi3(std::move(vdi2));
std::cout << "vdi2 before:" << std::endl;
for(auto it : vdi2)
std::cout << it << std::endl;
std::cout << "vdi3 before:" << std::endl;
for(auto it : vdi3)
std::cout << it << std::endl;
return 0;
}
If I remove the initializer_list constructor vdi2 won't compile. So my question: Why is the initializer_list not deduced by the template constructor? And is it possible to do so?
why is the initializer_list not deduced by the templated constructor?
The reason is that {1,2,3,4,5,6,7,8,9} is just a synctatic construct that doesn't have a type. Therefore, the compiler cannot deduce a type T for this synctatic construct and the first constructor fails.
However, by special Standard rules std::initializer_list<T> (among other things) can be construct from this synctatic construct and T can be deduced to int. Hence the second constructor works.
By constrast with function template argument type deduction, with
auto x = {1,2,3,4,5,6,7,8,9};
the compiler sets the type of x to be std::initializer_list<int>. There are also special Standard rules that says it must be so. Strictly speaking this is not type deduction because, as said above, {1,2,3,4,5,6,7,8,9} doesn't have a type to be deduced. (The only type deduction happening here is T = int in std::initializer_list<T>.) Here the compiler chooses (it doesn't deduce) the type of x to be std::initializer_list<int>. In any case, there's no harm to use the abuse of language of saying that the type of x is deduced to std::initializer_list<int>.
Finally, as DyP said in the comments, what you probably want is inheriting all constructors (not only those taking one argument) from the base container class. You can do this by removing all the constructors that you currently have and add just this line to test:
using container_type::container_type;
Related
This snippet of code is taken from https://en.cppreference.com/w/cpp/utility/variant/visit
using var_t = std::variant<int, long, double, std::string>;
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
for (auto& v: vec) {
// 4. another type-matching visitor: a class with 3 overloaded operator()'s
// Note: The `(auto arg)` template operator() will bind to `int` and `long`
// in this case, but in its absence the `(double arg)` operator()
// *will also* bind to `int` and `long` because both are implicitly
// convertible to double. When using this form, care has to be taken
// that implicit conversions are handled correctly.
std::visit(overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
}, v);
}
Can someone explain what using Ts::operator()...; means here?
And in the following, what constructor is this calling? with the 3 lambda functions?
overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
}
I think the concrete overloaded instance is deriving from all 3 of these function types, and then the visitor is picking the right one to use depending on the type of the variant. Is that right?
I just don't fully understand this example.
It creates a struct template called overloaded which inherits from all its template arguments. Next, it pulls all declared operator() functions from its base classes into its own scope, so these all take part in overload resolution when a user calls operator() on an instance of the overloaded struct.
The ... uses parameter pack expansion to perform the same operation for all template arguments.
And in the following, what constructor is this calling? with the 3 lambda functions?
It’s not a constructor, it’s aggregate initialisation. In this case it uses class template argument deduction (CTAD) to deduce the template arguments of overloaded and initialise its base class instances. Minus the CTAD, the aggregate initialisation is identical to this case:
struct A {};
struct B {};
struct C : A, B {};
void f() {
C c{A{}, B{}};
}
In effect you’re creating an instance of the overloaded struct template, directly initialise an object for it with the given base class objects, and pass that to std::visit. The net effect is as if you had defined a struct with multiple operator() overloads (which is the normal way of using std::visit).
#include <iostream>
struct uct
{
uct() { std::cerr << "default" << std::endl; }
uct(const uct &) { std::cerr << "copy" << std::endl; }
uct( uct&&) { std::cerr << "move" << std::endl; }
uct(const int &) { std::cerr << "int" << std::endl; }
uct( int &&) { std::cerr << "int" << std::endl; }
template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
};
int main()
{
uct u1 ; // default
uct u2( 5); // int
uct u3(u1); // template, why?
}
coliru
Template overload of the constructor fits to both declarations (u2 and u3). But when int is passed to the constructor, a non-template overload is chosen. When the copy constructor is called, a template overload is chosen. As far as I know, a non-template function is always preferred to a template function during overload resolution. Why is the copy constructor handled differently?
As far as I know non-template function is always preferred to template function during overload resolution.
This is true, only when the specialization and the non template are exactly the same. This is not the case here though. When you call uct u3(u1) The overload sets gets
uct(const uct &)
uct(uct &) // from the template
Now, since u1 is not const it would have to apply a const transformation to call the copy constructor. To call the template specialization it needs to do nothing since it is an exact match. That means the template wins as it is the better match.
To stop this one thing you can do is use SFINAE to limit the template function to only be called when T is not a uct. That would look like
template <typename T, std::enable_if_t<!std::is_same_v<uct, std::decay_t<T>>, bool> = true>
uct(T &&) { std::cerr << "template" << std::endl; }
When copy constructor is tried to be called, template overload is
chosen. As far as I know non-template function is always preferred to
template function during overload resolution. Why is copy constructor
handled differently?
template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
// ^^
The reason the templated version gets picked is because the compiler is able
to generate a constructor with signature (T &) which fits better and therefore is chosen.
If you changed the signature from uct u1 to const uct u1 then it would fit the copy constructor (since u1 is not const to begin with).
If you changed the signature from uct(const uct &) to uct(uct&) it would be a better fit and it would choose that over the templated version.
Also, the uct(uct&&) would be chosen if you had used uct u3(std::move(u1));
To fix this you can use SFINAE to disable the overload when T is the same as uct:
template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, uct>>>
uct(T&&)
{
std::cerr << "template" << std::endl;
}
The problem is that the template constructor has no the qualification const while the non-template copy constructor has the qualifier const in its parameter. If you will declare the object u1 as a const object then the non-template copy constructor will be called.
From the C++ STandard (7 Standard conversions)
1 Standard conversions are implicit conversions with built-in meaning.
Clause 7 enumerates the full set of such conversions. A standard
conversion sequence is a sequence of standard conversions in the
following order:
(1.4) — Zero or one qualification conversion
So the copy constructor needs one standard conversion while the template constructor sies not require such a conversion.
At the end of this video (starting at 15:57) there is advice on how to use almost uniform initialization in C++17: video here
The gist goes like this: use always direct initialization auto a{...}; and MyType a{...};
Do not use copy initialization = {...} for your types.
#include <iostream>
struct MyType {
explicit MyType(std::initializer_list<int>) {
std::cout << "Called std::initializer_list<int>" << std::endl;
}
explicit MyType(int) {
std::cout << "Called int." << std::endl;
}
MyType(int, int, int) {
std::cout << "Called int, int, int" << std::endl;
}
};
int main() {
MyType calls_init_list{10}; //Calls initializer_list<int>
MyType calls_init_list_2{10, 20}; //Calls initializer_list<int>
MyType calls_init_list_3{10, 20, 30}; //Calls initializer_list<int>
MyType compile_error = {10, 20, 30}; //Compile error
}
If I remove explicit from the first constructor it will call the 4th call also with initializer_list<int>
What changes should I need for being able to call (int) and (int, int, int) following the rule in the video?
Is it even possible to call the other constructors in the presence of the initializer list constructor?
any design recommendations to avoid abandoning the general rule adviced in the video? It would be nice to finally have something that makes sense, C++ initialization is the most terrible part of it probably.
What changes should I need for being able to call (int) and (int, int, int) following the rule in the video?
Remove the initializer_list<int> constructor. That is the only way to make it work.
Is it even possible to call the other constructors in the presence of the initializer list constructor?
Yes, so long as the types in the braced-init-list cannot match those in the any initializer_list<T> constructors. They always have primacy.
Hence why it's derisively called "almost uniform initialization".
The typical solution is to add some tag type to the non-initializer_list constructors:
struct tag_t {};
constexpr inline tag_t tag;
struct MyType {
explicit MyType(std::initializer_list<int>) {
std::cout << "Called std::initializer_list<int>" << std::endl;
}
MyType(tag_t, int) {
std::cout << "Called int." << std::endl;
}
MyType(tag_t, int, int, int) {
std::cout << "Called int, int, int" << std::endl;
}
};
int main() {
MyType three_int = {tag, 10, 20, 30}; //Calls 3-`int` constructor
}
any design recommendations to avoid abandoning the general rule adviced in the video?
Well, considering that the "general rule" is not a good rule (his slide contains the quintessential counter-example: try to call the size+value version of vector<int> with braces), it's better to abandon it. Minor quibbles about what auto a{2}; translates into are irrelevant next to being literally incapable of calling some constructors.
In your case, to call MyType(int, int, int) or explicit MyType(int), you have to use () syntax instead of {}.
Basically, I don't think it's a good idea to always use {} syntax. For example, as of C++17, all emplace methods in the standard library are using () internally instead of {}. For example, the code
std::vector<std::vector<int>> vv;
vv.emplace_back(2, 1);
emplaces <1, 1> not <2, 1>. That's also why standard containers do not support emplace construction of aggregate types.
In my opinion, genuine uniform initialization that you can stick to is one that performs () initialization if possible, and falls back to {} otherwise (e.g., for aggregate types). Also see this. Possible implementation:
template <typename...>
struct paren_initable: std::false_type {};
template <typename T, typename... Us>
struct paren_initable<decltype((void)T(std::declval<Us>()...)), T, Us...>
: std::true_type {};
template <typename T, typename... Us>
inline constexpr bool paren_initable_v = paren_initable<void, T, Us...>::value;
template <typename T, typename... Us>
T emplace(Us&&... us) {
if constexpr (paren_initable_v<T, Us...>) {
return T(std::forward<Us>(us)...);
}
else {
return T{std::forward<Us>(us)...};
}
}
I was looking at the article on std::variant http://en.cppreference.com/w/cpp/utility/variant/visit
The example contains essentially the following lines (sligtly modified by me):
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
auto a = overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
};
The code basically uses each lambda function in a list as a base class for the struct overloaded. The first line pulls lambda's operator() into the scope of the struct. The second line uses class template argument deduction guides (C++17).
question
I do not understand the line #3 with the use of { } braces after the overloaded.
What kind of C++ mechanism works here?
Do we use the initialization list and convert it into a variadic template parameters or it is a kind of uniform/aggregate initialization? Is any actual constructor being called in this line?
Interestingly, the construction fails if I use ( ).
It's aggregate initialization, specifically of the direct public bases of the resulting type. Since C++17, the base subobjects don't have to be aggregates themselves, they are copy initialised from the elements of the initialiser list.
In the current state of c++11 (say gcc 4.7.2), how should I choose between using a variadic-template or a std::initializer_list when I need a constructor that can take variable arguments?
A variadic template allows you providing arguments of different types, while an std::initializer_list is templated with the type of the argument. This means the type of all the elements in the list must be the same (or convertible to the underlying type, but no narrowing conversions are allowed). Depending on whether or not this is desirable for you, you may choose one or the other.
Also, a variadic template is usually the default choice if you need perfect forwarding, in that the syntactic form T&& can bind to both lvalue references and rvalue references, while a similar type deduction cannot be performed for initializer_list:
struct A
{
// Deduces T& for lvalue references, T for rvalue references, and binds to both
template<typename... Ts>
A(Ts&&...) { }
// This is an rvalue reference to an initializer_list. The above type deduction
// does not apply here
template<typename T>
A(initializer_list<T>&&) { }
};
Also notice, that a constructor accepting an initializer_list will be invoked by default when you use uniform initialization syntax (i.e. curly braces), even though another viable constructor exists. This may or may not be something you wish to have:
struct A
{
A(int i) { }
};
struct B
{
B(int) { }
B(std::initializer_list<A>) { }
};
int main()
{
B b {1}; // Will invoke the constructor accepting initializer_list
}
With a variadic template, the number of arguments is known during compilation (and accessible via sizeof...). With a std::initializer_list, the number of arguments is known only at runtime. So part of the decision depends on when you need or want to know how many arguments you have.
I recommend always chosing variadic templates and avoid std::initializer_list whenever possible.
This is how I would have implemented std::vector with C++11:
#include <iostream>
#include <vector>
struct exp_sequence {
template <typename... T>
exp_sequence(T&&...) {}
};
struct from_arglist_t {} from_arglist;
template <typename T>
class my_vector {
std::vector<T> data;
public:
my_vector(int n, T const& e) : data(n, e) {}
template <typename... Args>
my_vector(from_arglist_t, Args&&... args) {
data.reserve(sizeof...(Args));
exp_sequence{(data.push_back(std::forward<Args>(args)),1)...};
}
std::size_t size() { return data.size(); }
};
int main()
{
std::vector<int> v1{13, 13}; std::cout << v1.size() << '\n'; // 2
std::vector<int> v2(13, 13); std::cout << v2.size() << '\n'; // 13
my_vector<int> v3{13, 13}; std::cout << v3.size() << '\n'; // 13
my_vector<int> v4(13, 13); std::cout << v4.size() << '\n'; // 13
my_vector<int> v5(from_arglist, 13, 13); std::cout << v5.size() << '\n'; // 2
my_vector<int> v6{from_arglist, 13, 13}; std::cout << v6.size() << '\n'; // 2
}
The reason is as showed in main, using initializer_list in generic code can lead to different behaviour depending on which type of parentheses was chosen. There is also the possibility to silently change code by adding such an constructor.
Another reason are move-only types:
//std::vector<move_only> m1{move_only{}}; // won't compile
my_vector<move_only> m2{from_arglist, move_only{}}; // works fine