I would like to initialize a class with a mix of basic types, arrays and sub-structures like
A a { 1, // int
1.2, // double
"Hello", // const char *
{ 1, 2, 3 }, // array<int>
{ 1.2, 2.4 } // array<double>
{ 1, 1.2, "Hello" } // sub-structure of any types
};
where the sub-structure should be able to contain nested sub-structures as well.
For the basic types I can do that with variadic templates:
#include <iostream>
class A {
public:
template <class T, class... Args>
void print(T v) {
std::cout << v << std::endl;
}
template <class T, class... Args>
void print(T v, Args... args) {
std::cout << v << std::endl;
print(args...);
};
template <class T, class... Args>
A(T v, Args... args) {
print(v, args...);
};
};
int main() {
A a { 1,
1.2,
"Hello",
};
}
which correctly produces
1
1.2
Hello
But I fail for arrays and sub-structures. I'm restricted to C++11. Any help highly appreciated.
{/*..*/} has no type, and so cannot be deduced in template except for std::initializer_list<T> or C-array T(&)[N].
So for variadic template, as you cannot provide all those combination of overloads, you have to provide type, and as you are using template constructor, only in the type itself:
A a { 1, // int
1.2, // double
"Hello", // const char *
std::array<int, 3>{1, 2, 3 }, // array<int>
std::array<double, 2>{ 1.2, 2.4 } // array<double>
MyClass{ 1, 1.2, "Hello" } // sub-structure of any types
};
C++17, with CTAD, allows std::array{1, 2, 3} instead of std::array<int, 3>{1, 2, 3}.
From cppreference:
A braced-init-list is not an expression and therefore has no type,
e.g. decltype({1,2}) is ill-formed. Having no type implies that
template type deduction cannot deduce a type that matches a
braced-init-list, so given the declaration template void
f(T); the expression f({1,2,3}) is ill-formed.
So, as #Jarod42 has mentioned, you need to explicitly provide the type. You can also use an auto variable beforehand to deduce the type (e.g. into std::initializer_list<int>) and then pass the variable into the constructor.
This won't fit in a comment so I post it as an answer. What you call a main structure isn't a structure (or type) at all but a form on initialization. Namely list initialization. The values between the braces are matched against your constructor and since you have a constructor with variadic template arguments, your code compiles. Another reason is, that you only provide literals, and literals have very well defined types: int, const char[N], float, ... . The compiler already knows those types without you having to provide a type.
Now add another level of braces, for example:
{ 1, 2, { 3, 4, 5 }, "test" }
According to the rules of list initialization (which is used because of your outer pair of braces), the compiler now looks for a constructor that matches this signature:
Ctor(int, int, <unknown>, const char*)
As you can see the compiler doesn't know what type {3, 4, 5} is so it tries to find a constructor that at least matches all other arguments. If it finds one, it tries to pass the <unknown> part to whatever type the matched overload has at that position in the parameter list. This will then trigger another list initialization on that specific parameter and the whole process repeats itself.
Lets take our example list from above
{ 1, 2, { 3, 4, 5 }, "test" }
and a set of constructors
Ctor(int, float*, int, const char*); // A
Ctor(int, int, int, const char*); // B
Ctor(int, int, std::array<int, 3>, const char*); // C
Now the compiler tries to match all known types to the imaginary signature we defined above (yes const char* is not the actual type of the literal but we ignore that here):
Ctor(int, int, <unknown>, const char*)
Option A is ruled out immediately, because the types of the 2nd argument do not match, so we're left with B and C. For B and C both signatures match the known types, so we're left figuring out what <unknown> could be. B offers us an int and C std::array<int, 3>. Lets check out B: can we do int x{3, 4, 5} (this is a simplification and just for illustration purposes)? Turns out C++ doesn't allow us to do that, so we discard B as an option. Next is C: can we do std::array<int, 3> x{3, 4, 5}. That looks right, C++ allows us to do that. We found a possible match for <unknown> and the compiler selects C as the constructor to be invoked. Great!
Now lets declare a variadic template constructor
template <typename ...Args>
Ctor(Args&&... args);
and match that against our list. Since we have template parameters, we basically tell the compiler: take whatever type the argument gives us. So we do that. The first two and the last arguments are easy, they're just literals so we have
template <typename Arg3>
Ctor(int, int, Arg3&& arg3, const char*);
Now we try out our nested list. The argument is <unknown> because the compiler couldn't figure out what {3, 4, 5} is (we leave std::initializer_list out here as a special case). The template parameter says: take whatever the type of that argument is, and the argument type is <unknown>. Do you spot the problem?
I post this as an answer since I need code formatting. I fully understand that the way I want to do this does not work. I would not have started if I would not have seen this before:
#include <iostream>
#include <json.hpp>
using json = nlohmann::json;
int main() {
json j {
{ "Float", 1.2},
{ "Int", 2},
{ "List", { 1,2,3 }},
{ "Object", {
{ "First", 3 },
{ "Name", "Joe"}
}}
};
std::cout << j << std::endl;
}
This is from https://github.com/nlohmann/json and used varadic templates. It nicely produces this output:
{"Float":1.2,"Int":2,"List":[1,2,3],"Object":{"First":3,"Name":"Joe"}}
So there must be a way to make this work. Unfortunately my mind is too poor to understand the code used by that library.
Related
Please help me to understand why a std::initializer_list literal can't be deduced as a template parameter? AFAIK, there's no notion of an init-list literal in the language yet, but then why/how does this work?
auto il = { 1, 2, 3, 4, 5 };
Here is my code:
import <iostream>;
import <string>;
import <vector>;
template <typename T>
constexpr auto type_name() {
std::string_view name, prefix, suffix;
#ifdef __clang__
name = __PRETTY_FUNCTION__;
prefix = "auto type_name() [T = ";
suffix = "]";
#elif defined(__GNUC__)
name = __PRETTY_FUNCTION__;
prefix = "constexpr auto type_name() [with T = ";
suffix = "]";
#elif defined(_MSC_VER)
name = __FUNCSIG__;
prefix = "auto __cdecl type_name<";
suffix = ">(void)";
#endif
name.remove_prefix(prefix.size());
name.remove_suffix(suffix.size());
return name;
}
template< typename T >
int SAI_BF_helper(T&&) { return 0; }
int SAI_BF_helper(int i) { return i; }
// function that take any number parameters of any type and then return sum of all ints using binary left folding expressions
template< typename ... Types >
¡int SumAllInts_BinaryLeftFold(Types ... args)
{
return (0 + ... + SAI_BF_helper(args));
}
template< typename T >
void PrintTypeName(T&& t)
{
std::cout << type_name< decltype( std::forward< T >(t) )> () << std::endl;
}
// if this overload is removed then 'PrintTypeName({1,2,3});' code will not compile
template< typename T >
void PrintTypeName( std::initializer_list< T >&& t)
{
std::cout << type_name< decltype(std::forward< std::initializer_list< T > >(t))>() << std::endl;
}
int main()
{
std::vector< int > numbers{ 1, 2, 3 };
auto il = { 1, 2, 3, 4, 5 };
PrintTypeName(numbers); // output: class std::vector<int,class std::allocator<int> >&
PrintTypeName(il); // output: class std::initializer_list<int>&
PrintTypeName({1,2,3}); // output: class std::initializer_list<int>&&
std::cout << SumAllInts_BinaryLeftFold() << std::endl; // 0
std::cout << SumAllInts_BinaryLeftFold("", 0, 1, 2.2, 'a', "char*", 10, std::string("str"), numbers, il) << std::endl; // 11
//std::cout << SumAllInts_BinaryLeftFold( 1, {1, 2}) << std::endl; // MSVC error message: 'initializer list': is not a valid template argument for 'Types'
}
The short answer: initializer lists (the language mechanic, not the type) are magic. No, really.
The C++ standard has explicit wording for initializer-list syntax to produce a std::initializer_list object in certain circumstances such as constructing an auto-parameter, but an initializer list expression is not itself always a std::initializer_list; it depends on the circumstances.
This distinction is needed because the same syntax may be used in place of implicit construction for cases like parameters. For example, consider the following code:
auto do_something(Person) -> void;
...
do_something({}); // Calls Person() -- not Person(std::initializer_list<U>)
In this, {} isn't meant to be a std::initializer_list object; it's an implicit default-construction of Person.
Because these cases exist, deducing the exact type of a brace-enclosed initializer list is not so straight-forward. For example:
template <typename T>
auto do_something(T) -> void;
do_something({1}) -> void;
In the above case, what should T be? Is it do_something<std::initializer_list<int>>? do_something<int> with int{1}? What if this were {1, 2U}? It can get complicated.
There is no such thing as a "std::initializer_list literal". {1, 2, 3} is a braced-init-list; a syntactic construct with no type that can be used to initialize many types. There are a few specific situations (see below) where a std::initializer_list will be implicitly created from a braced-init-list, but they are not the same thing.
From cppreference :
A std::initializer_list object is automatically constructed when:
a braced-init-list is used to list-initialize an object, where the corresponding constructor accepts an std::initializer_list parameter
a braced-init-list is used as the right operand of assignment or as a function call argument, and the corresponding assignment operator/function accepts an std::initializer_list parameter
a braced-init-list is bound to auto, including in a ranged for loop
auto il = {1, 2, 3, 4, 5};
This is one of the situations when a std::initializer_list will be implicitly created from a braced-init-list. A braced-init-list is being bound to auto.
template <typename T>
void PrintTypeName(std::initializer_list<T>&& t)
{
...
}
PrintTypeName({1,2,3});
This is another of the specific situations where a braced-init-list is automatically converted to std::initializer_list. A braced-init-list is being used as a function call argument and the corresponding function accepts a std::initializer_list parameter.
template<typename T>
void PrintTypeName(T&& t)
{
...
}
PrintTypeName({1,2,3});
This is not one of the situations where a braced-init-list will automatically be converted to a std::initializer_list. This PrintTypeName does not specifically accept a std::initializer_list.
I would like to be able to initialize my objects with a brace-init-list:
#include <initializer_list>
template <class T>
struct S {
T v[5];
S(std::initializer_list<T> l) {
int ind = 0;
for (auto &&i: l){
v[ind] = i;
ind++;
}
}
};
int main()
{
S<int> s = {1, 2, 3, 4, 5};
}
As I found out here: https://en.cppreference.com/w/cpp/utility/initializer_list, it is necessary to use the standard library for this.
But that is strange for me. I would suggest that such initialization is a part of the C++ syntax.
Is it possible to create a constructor without use of std:initializer_list?
Edit:
It could be useful for programming embedding devices where standard library is not available (e.g. Arduino AVR).
As a work around one could do something like
#include <iostream>
template <class T>
struct S {
T v[5];
template<typename... Args>
requires (std::same_as<T, Args> && ...)
S(Args... args) {
static_assert(sizeof...(args) <= 5);
int ind = 0;
((v[ind++] = args), ...);
}
};
int main()
{
S<int> s = {1, 2, 3, 4, 5};
for ( auto e : s.v ) {
std::cout << e << ' ';
}
}
live on godbolt
As RemyLebeau pointed out in the comments a constructor like S(T,T,T,T,T) is needed. With a variadic constructor template and C++17 fold expressions you can make the compiler provide a constructor of that form for each number of arguments.
Note that the requires constraint (C++20) is important if you want other constructors in that class. It constraints the template the form S(T,T,T,...) and will not take part in overload resolution for arguments, that are not all of type T. It should also be possible to achieve that pre C++20 with std::enable_if*.
Also remember that the compiler will instantiate a constructor with matching signature for each call with a different number of arguments. Thus this could result in code bloat if used often and with many different numbers of arguments in the braces.
* like this for example:
template<typename... Args,
typename std::enable_if_t<(std::is_same_v<T, Args> && ...), bool> = true>
S(Args... args) {
//...
}
I stumbled upon this code in the SerenityOS project:
template<typename... Parameters>
void dbgln(CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters)
They are re-implementing an equivalent of println! from rust. An easier version of printf where you don't need to care about the argument type and is used this way:
dbgln("This is a {}", "test");
They are doing some compile time checks on the fmtstr making sure that there are no unclosed braces and that the number of braces match the number of arguments. That's why the FormatString struct need to be templated to have access to the number of argument at compile time.
But there is something I don't understand. I wrote an MWE with the code below which, in essence, reproduce what they are doing:
#include <stddef.h>
template<typename... Args>
void compiletime_fail(Args...);
template<typename ...Parameters>
struct UnconstrainedFormatString {
template <size_t size>
consteval UnconstrainedFormatString(const char (&buffer)[size]): m_buffer(buffer), m_size(size) {
}
const char *m_buffer { nullptr };
const size_t m_size { 0 };
};
template<typename T>
struct __IdentityType {
using Type = T;
};
template<typename T>
using IdentityType = typename __IdentityType<T>::Type;
template<typename... Args>
using FormatString = UnconstrainedFormatString<IdentityType<Args>...>; // but why?
template<typename ...Parameters>
constexpr void println(FormatString<Parameters...>&& fmtstr, const Parameters& ...parameters) {
}
int main() {
println("this is a test", 1, 2, 3);
}
if I used UnconstrainedFormatString in the println signature I get this error from the compiler:
/cplayground/code.cpp:32:3: error: no matching function for call to 'println'
println("this is a test", 1, 2, 3);
^~~~~~~
/cplayground/code.cpp:28:16: note: candidate template ignored: could not match 'UnconstrainedFormatString<type-parameter-0-0...>' against 'char const[15]'
constexpr void println(UnconstrainedFormatString<Parameters...>&& fmtstr, const Parameters& ...parameters) {
In order for it to compile, I need to do that funky business with IdentityType.
Why do I need this?
See the https://en.cppreference.com/w/cpp/language/template_argument_deduction#Implicit_conversions :
Type deduction does not consider implicit conversions (other than type
adjustments listed above): that's the job for overload resolution,
which happens later.
How does the FormatString/IdentityType change things? The IdentityType suppresses the deduction of template parameters for const char[N] and now the string is used as a constructor parameter for UnconstrainedFormatString<...>.
You can learn more details here:
https://humanreadablemag.com/issues/0/articles/how-to-avoid-template-type-deduction-in-c
When you use UnconstrainedFormatString only, the compiler tries to deduct UnconstrainedFormatString from const char [N] and fails since for templates it "doesn't know" that the conversion from const char[N] to UnconstrainedFormatString exists.
You can easily check with the
println(UnconstrainedFormatString<int, int, int>("test"), 1, 2, 3);
Since no conversion is needed here, it works, as well.
I have the following (not compilable) code:
template< size_t N >
void foo( std::array<int, N> )
{
// Code, where "N" is used.
}
int main()
{
foo( { 1,2 } );
}
Here, I want to pass an arbitrary number of ints to a function foo -- for convenience, I will use the std::initializer_list notation.
I tried to use an std::array to aggregate the ints (as shown in the code above), however, the compiler can not deduce the array size since the ints are passed as an std::initializer_list.
Using an std::initializer_list instead of an std::array also does not solve the problem since (in contrast to std::array) the size of the std::initializer_list is not captured as template argument.
Does anyone know which data structure can be used so that the ints can be passed by using the std::initializer_list notation and without passing the template argument N of foo explicitly?
Many thanks in advance
Thanks to core issue 1591, you can use
template <std::size_t N>
void foo( int const (&arr)[N] )
{
// Code, where "N" is used.
}
foo({1, 2, 3});
If using an initializer list is not a must, you can use variadic template parameter packs:
template<size_t S>
void foo_impl(array<int, S> const&)
{
cout << __PRETTY_FUNCTION__ << endl;
}
template<typename... Vals>
auto foo(Vals&&... vals) {
foo_impl<sizeof...(vals)>({ std::forward<Vals>(vals)... });
}
you'd call it as follows:
foo(1,2,3,4,5);
This defers common type checks until the initialization point of std::array (unless you add some admittedly ugly asserts), so you probably should prefer Columbo's answer.
I encountered a problem which i dont really know how to solve. I want to pass variadic ammount of pairs in constructor, using { } braces, but for some reason im gettin compialtion error. I kept class as simple as possible
class MyPairs
{
public:
MyPairs(std::pair<char, int>&& pair)
{
pairs[pair.first] = pair.second;
}
template <typename ... Args>
MyPairs(std::pair<char, int>&& pair, Args&& ... args)
{
pairs[pair.first] = pair.second;
MyPairs(std::forward<Args>(args)...);
}
private:
std::map<char, int> pairs;
};
int main()
{
MyPairs p1( // okay, constructor expects pairs
std::pair<char, int>('a', 1),
std::pair<char, int>('b', 2)
);
MyPairs p2( // okay, compiler knows that it is std::pair<char, int> in constructor
{ 'c',3 }
);
MyPairs p3( // error, for some reason now he doesnt know it is std::pair<char, int>, std::pair<char, int>
{ 'd',6 },
{ 'e',5 }
);
}
Of course i can write std::pair<char, int>('a', 1) for every argument, but {'a', 1} is much more conventient.
Why compiler doesnt know how to match function when using { } braces ?
The easiest way is probably to use list initialization, and provide a constructor that accepts an std::initializer_list<T>.
In fact, that's how std::map's constructor (5) does it.
For example:
MyPairs(std::initializer_list<std::map<char, int>::value_type> init) : pairs(init) {}
See it live on Coliru
The only tricky bit here is that we cannot use a std::intializer_list<std::pair<char, int>> (because std::map<Key, T> defines value_type as std::pair<const Key, T>), since that will not be convertible to the std::intializer_list<std::pair<const char, int>> map's constructor is expecting.
To focus on this question:
Why compiler doesn't know how to match function when using { } braces?
The reason is because each parameter is deduced separately. For example, consider this:
template <typename A, typename B>
void foo(A a, B b) {
}
If I call foo(1, 3.14) then A will be deduced as int and B will be deduced as double. The two parameters are treated totally separately. The fact that the first one is int does not tell us anything about the second parameter. The compiler has to deduce the second parameter all on its own, independent of the first parameter.
This same logic applies to variadic templates. Each parameter in a variadic template is treated individually. The fact that the first parameter to your constructor is a std::pair<char, int> tells you nothing about the later parameters. You could, after all, try this:
MyPairs p(
{ 'd',6 },
1,
3.14,
"Hello, world!"
);
and the compiler would call the variadic constructor and deduce the variadic arguments as int, double, and a string. Each parameter is treated individually.
So when you try the following:
MyPairs p(
{ 'd',6 },
{ 'e',5 }
);
The compiler has to try and deduce the type of { 'e',5 }. And what type should it have? Remember, the compiler has to deduce its type separately from the first parameter. It's clear to us humans that you want it to be std::pair, but there compiler does not know that. So the compiler gives an error saying it doesn't know what to do with those extra parameters.
To give a slightly simpler example, your code is roughly equivalent to doing this:
template <typename A>
void bar(A a) {
}
bar({ 'd', 6 });
It's not clear to the compiler that you want A to be deduced as a std::pair. It could, after all, be a whole lot of different things.