I have the following templated code
#include <vector>
#include <array>
#include <iostream>
template<typename T1>
void foo(std::vector<T1> bar) {
std::cout << "GENERIC" << std::endl;
}
template<typename T1>
void foo(std::vector<std::vector<T1>> bar) {
std::cout << "SPECIFIC (vector)" << std::endl;
}
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::vector<int>> a(2, std::vector<int> { 1, 2, 3});
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo(a);
foo(b);
}
which produces
SPECIFIC (vector)
GENERIC
I'm wondering why the vector-of-vector version is called with the specific template, but the vector-of-array version is called with the generic?
template<typename T1, size_t SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
You should use std::size_t instead of int.
run here
Edit :
Actually, your comments and my intuition about the code led me to dig into the topic. At first glance, a standard developer ( like me ) expect compiler to convert int to std::size_t(because they are both integral type and implicitly converting is very trivial) and select void foo(std::vector<std::array<T1, SIZE>> bar) as best specialization. So while reading template argument deduction page i found this :
If a non-type template parameter is used in the parameter list, and
the corresponding template argument is deduced, the type of the
deduced template argument ( as specified in its enclosing template
parameter list, meaning references are preserved) must match the type
of the non-type template parameter exactly, except that cv-qualifiers
are dropped, and except where the template argument is deduced from an
array bound—in that case any integral type is allowed, even bool
though it would always become true:
As always, of course, you must read few more times than once to understand what it means :)
So an interesting result comes out.
Already our desired specialization is not selected but if the compiler had been forced to select, it would be an error.
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo(b); // P = std::vector<std::array<int,(int)SIZE>
// A = std::vector<std::array<int,(unsigned_long)SIZE>>
// error: deduced non-type template argument does not have the same
// type as its corresponding template argument */
}
run code
Another interesting thing is :
If the non-type template argument had not been deduced, there would be no restriction which forces argument and template types to be same.
#include <vector>
#include <array>
#include <iostream>
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo<int,3>(b);
}
run code
I think this is simply due to one line from [temp.deduct.call]/4
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A
To clarify, A means the parameter, from [temp.deduct.call]/1
...template argument deduction with the type of the corresponding argument of the call (call it A)...
As has already been pointed out, changing template<typename T1, int SIZE> to template<typename T1, size_t SIZE> fixes the issue you are seeing. As stated in [temp.deduct.call]/4, the compiler is looking to deduce an A that is identical to A. Since an std::array has template arguments <class T, size_t N> (from [array.syn]), it's second parameter is in fact size_t, not int.
Therefore, for the template deduction, your generic function of template<typename T1> is able to match exactly the type of A, where-as your specialized template<typename T1, int SIZE> is not an exact match. I believe MSVC is incorrect in its deduction.
Related
Simple example first, doesn't compile with GCC12:
#include <array>
#include <iostream>
#include <initializer_list>
template <int nvars> class Test1 {
public:
Test1( std::initializer_list<int> IL ) : arr { 0 }
{
// See https://stackoverflow.com/questions/38932089/can-i-initialize-an-array-using-the-stdinitializer-list-instead-of-brace-enclo
};
const std::array<int,nvars> arr;
};
// CTAD guide / simple-template-id
Test1(std::initializer_list<int> IL) -> Test1<IL.size()>;
int main() {
Test1 test1({1,2,3,4});
std::cout << "nvars: " << test1.arr.size() << std::endl;
}
The simple-template_id here is Test1<IL.size()>, where IL.size() is a constexpr function returning 4. It does compile with a hardcoded 4, but that obviously defeats the purpose.
What is and is not allowed in the simple-template_id of the CTAD guide? Which names, which types of expressions?
The problem is that parameters like IL are not constexpr. This means that we can't use IL.size() as a template argument since template arguments are required to be compile time constant.
Thus to solve this we have to pass a compile time constant as a template argument. One way of doing this would be to make the parameter as a reference to an array of size N with elements of type T where both T and N are template parameters.
template<typename T, std::size_t N> Test1(T const(&&)[N]) -> Test1<N>;
#include <iostream>
#include <array>
#define print(x) std::cout << x
#define println(x) std::cout << x << std::endl
template<std::size_t Size>
void Print(std::array<int, Size>& arr) {
for (int i = 0; i < Size; i++) {
println(arr[i]);
}
}
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
Print(arr);
}
How does the size got passed to the function template without defining it like Print<5>(arr) ? (at line 7 "the actual template", at line 16 "calling the function")
How does the size got passed to the function template without defining it like Print<5>(arr) ?
It is thanks to template argument deduction. The size is deduced from the call Print(arr). Print() is a function template with a non-type template parameter (i.e., Size) of type std::size_t:
template<std::size_t Size>
void Print(std::array<int, Size>&);
That is, the template parameter Size is deduced to a value of type std::size_t, which corresponds to the second template argument of the std::array passed as a function argument when calling Print().
That’s called “template argument deduction” where it can figure it out based on what you’re passing in.
https://en.cppreference.com/w/cpp/language/template_argument_deduction
I would like to have a class like this:
template<typename T>
struct Foo {
T* data_;
template<typename... Ts, std::enable_if<std::is_same<T,Ts>...>...>
explicit Foo(Ts...ts) : data_{ ts... } {}
};
However; something with the syntax is wrong, and I'm not sure if you can set parameters into a pointer directly like this upon initialization.
What I would like for this to do is simply this:
Foo<int> f1{ 1, 3, 5, 7 }; // Or
// Foo<int> f1( 1, 3, 5 7 );
// f1.data_[0] = 1
// f1.data_[1] = 3
// f1.data_[2] = 5
// f1.data_[3] = 7
// f1.data_[4] = ... not our memory either garbage or undefined...
Foo<float> f2{ 3.5f, 7.2f, 9.8f }; // Or
// Foo<float> f2( 3.5f, 7.2f, 9.8f );
// f2.data_[0] = 3.5
// f2.data_[1] = 7.2
// f2.data_[2] = 9.8
// f2.data_[3] = ... not our memory
I would also like to have the constructor check to make sure that each and every parameter that is passed into the constructor is of type <T>; simply put for each Ts it must be a T.
I might be overthinking this but for the life of me I can not get this or something similar to compile. I don't know if it's within enable_if, is_same or through the class's initializer list and trying to store the contents into a pointer. I don't know if I should use an array of T instead but the array's size won't be known until the arguments are passed into the constructor. I'm also trying to do this without using a basic container such as std::vector; it's more for self education than practical source code. I just want to see how this could be done with raw pointers.
Edit
I've changed my class to something like this:
template<typename T>
struct Foo {
T* data_;
template<typename... Ts, std::enable_if_t<std::is_same<T, Ts...>::value>* = nullptr>
explicit Foo( const Ts&&... ts ) : data_{ std::move(ts)... } {}
};
And when trying to use it:
int a = 1, b = 3, c = 5, d = 7;
Foo<int> f1( a, b, c, d );
Foo<int> f2{ a, b, c, d };
I'm a little closer with this iteration; but they both give different compiler errors.
The first being: C2661: "No overloaded function takes 4 arguments"
And the second: C2440: "initializing, cannot convert from initializer list to Container, no constructor could take the source type, or constructor overload resolution was ambiguous."
Why not simply use a std::initialize_list:?
#include <iostream>
#include <type_traits>
#include <vector>
template <class T>
struct Foo
{
std::vector<T> data_;
explicit Foo(std::initializer_list<T> data) : data_(data)
{
std::cout << "1";
};
template <typename... Ts,
typename ENABLE=std::enable_if_t<(std::is_same_v<T,Ts> && ...)> >
explicit Foo(Ts... ts) : Foo(std::initializer_list<T>{ts...})
{
std::cout << "2";
}
};
int main()
{
Foo<int> f1{1, 3, 5, 7}; // prints 1
Foo<int> f2(1, 3, 5, 7); // prints 1 then 2
return 0;
}
If some Ts are different from T you will get a compile-time error.
With
gcc -std=c++17 prog.cpp
you get:
Foo<int> f1{1, 3, 5., 7};
error: narrowing conversion of ‘5.0e+0’ from ‘double’ to ‘int’ inside
{ } [-Wnarrowing] Foo f1{1, 3, 5., 7};
^
and
Foo<int> f2(1, 3, 5., 7);
you get
error: no matching function for call to ‘Foo::Foo(int, int,
double, int)’ Foo f2(1, 3, 5., 7);
^ note: candidate: ‘template Foo::Foo(Ts ...)’ explicit Foo(Ts... ts) :
Foo(std::initializer_list{ts...})
...
Update: if you really want to use something like raw pointer, here is a complete working example:
#include <iostream>
#include <memory>
#include <type_traits>
#include <vector>
template <class T>
struct Foo
{
size_t n_;
std::unique_ptr<T[]> data_;
explicit Foo(std::initializer_list<T> data) : n_(data.size()), data_(new T[n_])
{
std::copy(data.begin(), data.end(), data_.get());
std::cout << "1";
};
template <typename... Ts, typename ENABLE = std::enable_if_t<(std::is_same_v<T, Ts> && ...)> >
explicit Foo(Ts... ts) : Foo(std::initializer_list<T>{ts...})
{
std::cout << "2";
}
friend std::ostream& operator<<(std::ostream& out, const Foo<T>& toPrint)
{
for (size_t i = 0; i < toPrint.n_; i++)
std::cout << "\n" << toPrint.data_[i];
return out;
}
};
int main()
{
Foo<int> f1{1, 3, 5, 7}; // prints 1
Foo<int> f2(1, 3, 5, 7); // prints 1,2
std::cout << f1;
std::cout << f2;
return 0;
}
I let you replace unique_ptr by a raw pointer with all the extra work: delete[] etc...
std::is_same only compares two types, and you can't use pack expansions to declare multiple template parameters. That means you'll need to pull all of your std::is_same checks out into another check:
template <typename T, typename... Ts>
struct all_same : std::bool_constant<(std::is_same<T, Ts>::value && ...)> {};
template <typename T>
struct Foo
{
std::vector<T> data_;
template <typename... Ts, std::enable_if_t<all_same<T, std::decay_t<Ts>...>::value>* = nullptr>
Foo(Ts&&... ts)
: data_{std::forward<Ts>(ts)...}
{
}
};
Live Demo
You also need to allocate memory for your data_ array. Here I've used std::vector to take care of that allocation for me, but you could use new[] and delete[] to manage it yourself if you really want to.
enable_if and is_same won't store anything anywhere, they are only compile-time constructs and do not yield to any code in the binary executable.
Regardless of the syntax, what your code is essentially doing is trying to take the address of a constructor argument (which is a temporary). This will be a dangling pointer as soon as the constructor exits.
Either Foo owns the memory area and must allocate in constructor and delete in destructor (if any doubt: use std::vector!), or it aliases some external memory, and must receive a pointer to that memory.
Now regarding syntax:
std::is_same is a template that provides a value boolean constant and is to be used like so: std::is_same<T1, T2>::value. Alternatively you can use std::is_same_v<T1, T2>.
std::enable_if provides a type type member, only if the constant expression (1st template parameter) is true. Use it like std::enable_if<expr, T>::type. If expr is true, type is a typedef to T. Otherwise it is not defined and yields a substitution failure. Alternatively you can use std::enable_if_t<expr, T>
You can have a look here for a similar approach of yours.
But you can also simplify all this by using a member vector. In that case, the vector constructor ensures that all arguments have compatible types. Here is a complete example:
#include <string>
#include <vector>
#include <iostream>
#include <type_traits>
using namespace std;
template<typename T>
struct Foo {
vector<T> data_;
template<typename ...Ts>
explicit Foo(Ts... ts) : data_{ ts... } {}
void print() {
for (const auto &v : data_) {
cout << v << " ";
}
cout << endl;
}
};
int main() {
Foo<int> ints { 1, 2, 3, 4, 5 };
Foo<string> strings { "a", "b", "c", "d", "e"};
// Foo<string> incorrect { "a", 2, "c", 4, "e"};
ints.print();
strings.print();
// incorrect.print();
return 0;
}
The most idiomatic way to do this in C++17 is using std::cunjunction_v. It lazily evaluates subsequent values and allows you to avoid fold expressions. Also messages generated by the compiler are the same for both of casese you mentioned in edited piece of code.
Also, passing data by const-rvalue-ref makes no sense, since it is impossible to move data from const objects. Additionaly you were moving pack of arguments to a pointer. I didn't know what to do about it so just removed it.
Additionaly, take a look at the std::decay_t - without it it would not work, as sometimes Ts is deduced not as int but as const int & or int &. This leads to std::is_same_v being false.
The code below compiles fine at godbolt:
template<typename T>
struct Foo {
T* data_;
template<
typename... Ts,
std::enable_if_t<
std::conjunction_v<
std::is_same<T, std::decay_t<Ts>>...
>
> * = nullptr
>
explicit Foo( Ts&&... ts ) : data_{ } {}
};
EDIT: Problem solved, it was just wrong function declaration order.
when trying to solve a problem which seemed to be quite trivial at first glance, I stumbled over behaviour I can not explain.
I want to process an arbitrary int-array recursively and of course I have to stop the recursion somehow.
As partial specialisation to concrete numbers is not possible with template functions (like
template<typename T, int N> foo<T, 0>(void)
), I tried to fake this with SFINAE. But when I want to call the second SFNIAE-function from the first one, I get a compiler error.
Complete code example:
#include <algorithm>
#include <iostream>
using namespace std;
// -------------------------------------------------------------
template<typename T, int N>
void foo(T const& param, typename enable_if<N != 0, int>::type* = 0)
{
cout << "recursive step " << N << endl;
/* --- This was, what I desired: --- */
//foo<T, N - 1>(param);
/* --- THIS IS CAUSING AN ERROR! --- */
foo<T, 0>(param);
}
// -------------------------------------------------------------
template<typename T, int N>
void foo(T const& param, typename enable_if<N == 0, int>::type* = 0)
{
cout << "finish recursion" << endl;
}
// =============================================================
int main()
{
int a[5] = {0, 1, 2, 3, 4};
foo<decltype(a), 5>(a);
/* --- SAME CALL AS WITHIN foo(), BUT CAUSING NO ERROR! --- */
foo<decltype(a), 0>(a);
}
The compiler tells me:
main.cpp:9: Fehler: no type named 'type' in 'struct std::enable_if'
So it seems he somehow cannot solve for the second function.
However, if I call the function from main(), its not a problem.
Its my first time with SFINAE, I hope I made no trivial mistakes.
Thanks to everyone who read this far!
I'm pleasantly surprised with Clang's helpfulness on this one:
main.cpp:14:5: error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup
foo(param);
^
main.cpp:29:5: note: in instantiation of function template specialization 'foo' requested here
foo(a);
^
main.cpp:19:6: note: 'foo' should be declared prior to the call site
void foo(T const&, typename std::enable_if::type* = 0)
^
I mean, it's just one step away from logging in on Stack Overflow and giving the answer itself. Anyway.
Add the declaration of the second function above the first one, so you can call it from therein:
template<typename T, int N>
void foo(T const&, typename std::enable_if<N == 0, int>::type* = 0);
Note that you must remove the default argument from the definition as well.
You need to switch the order of the definition (or declare the trivial case before the normal case), the normal case does not see the trivial one so it cannot call it:
template<typename T, int N>
void foo(T const& param, typename enable_if<N == 0, int>::type* = 0) {
cout << "finish recursion" << endl;
}
template<typename T, int N>
void foo(T const& param, typename enable_if<N != 0, int>::type* = 0) {
cout << "recursive step " << N << endl;
foo<T, N - 1>(param);
}
Note that you could use a helper class here to avoid this enable_if (and also change the signature so that you could let the compiler deduce parameters):
template<typename T, int N>
struct foo_h {
static void call(T const& param) {
foo_h<T, N - 1>::call(param);
}
};
template<typename T>
struct foo_h<T, 0> {
static void call(T const& param) {
}
};
template<typename T, int N>
void foo(const T (¶m)[N]) {
foo_h<const T[N], N>::call(param);
}
Then:
int arr[] = {1, 2, 3, 4, 5};
foo(arr); // Automatic template parameters deduction!
In C++11, is it possible to do something similar to the following?
template<typename T, size_t N>
void foo(array<T, N> src) { ... }
...
foo({1, 2, 3})
I'm currently running GCC 4.8.
Yes, I managed to get the following work (since you allow something similar):
template<typename T, size_t N>
void foo(array<T, N> src) { ... }
...
foo('a', 'b');
foo(1, 2, 3);
Here is how:
#include <array>
#include <iostream>
#include <utility>
using namespace std;
template<typename T, unsigned long N>
void foo(array<T,N> src) {
for (auto e : src)
cout << e << endl;
}
template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
std::array<T, 1 + sizeof...(Tail)> a = {{ head, tail ... }};
return a;
}
template<class T, class... Tail>
void foo(T&& head, Tail&&... values) {
foo(make_array(std::forward<T>(head), std::forward<Tail>(values)...));
}
int main() {
foo('a', 'b');
foo(1, 2, 3);
}
I have tested this with gcc 4.7.2 and with clang 3.4 (trunk 184647), they work as expected.
Here is an online version at Stacked-Crooked. However, this code fails to compile at Ideone. Since I was unable to figure out the options passed to the compiler at Ideone, I've given up on that site.
I have shamelessly stolen the make_array function from #Pavel Minaev's answer to the How to emulate C array initialization “int arr[] = { e1, e2, e3, … }” behaviour with std::array? question. The other make_array suggestions caused compile errors that I couldn't fix.
This make_array function has limitations, please read the entire post; in particular the discussion std::array - if only it knew its size on comp.lang.c++.moderated is referenced. Apparently, getting a reasonable make_array is quite tricky. I wouldn't recommend the simple-minded make_array in this answer to be used in production code.
You wouldn't have any problems if the size was a template argument to std::initializer_list. Hence the question Why is the size not a template argument of std::initializer_list?
Apparently not. The standard (14.8.2.5) calls this an non-deduced context;
In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified.
...
The non-deduced contexts are:
...
A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list type.
Example:
template<class T> void g(T);
g({1,2,3}); // error: no argument deduced for T
EDIT: You can make the same thing work with std::vector, if you just use an initializer_list overload to make the deduction of the type work;
template<typename T>
void foo(const std::vector<T>& src) { ...your code here... }
template<typename T>
void foo(const std::initializer_list<T>& src) { foo(std::vector<T>(src)); }
foo({1,2,3}); // Compiles
...but sadly, since the size of initializer_list is not a template argument, I can't think of a way to make it deduce and forward the array size from the initializer_list in the same way as the type.
You could use an initializer list directly to achieve that syntax. e.g.:
#include <iostream>
#include <initializer_list>
void foo(std::initializer_list<int> il) {
for (auto i: il)
std::cout << i < std::endl;
}
int main() {
foo({1,2,3});
}
or make it more generic:
template <typename T>
void foo(std::initializer_list<T> il) {
...
It is possible with references to raw arrays:
template <typename T, size_t N>
void foo(T const (&x)[N]) {
// x is [1, 2, 3], N = 3
}
int main() {
foo({1, 2, 3});
return 0;
}
Note that the array must be declared const.