Perplexing non-trailing parameter pack behaviour - c++

I've come across some interesting variadic template function behaviour. Can anyone point out the relevant rules in the standard which define this?
GCC, ICC and MSVC compile the following code successfully (Clang doesn't, but I understand that this is due to compiler bugs).
template<class A, class... Bs, class C>
void foo(A, Bs..., C) { }
int main()
{
foo<int, int, int, int>(1, 2, 3, 4, 5);
}
In this call to foo, template arguments are provided for A and Bs, then C is deduced to be int.
However, if we simply flip the last two template parameters:
template<class A, class C, class... Bs>
void foo(A, Bs..., C) { }
Then all three compilers throw errors. Here is the one from GCC:
main.cpp: In function 'int main()':
main.cpp:8:42: error: no matching function for call to 'foo(int, int, int, int, int)'
foo<int, int, int, int>(1, 2, 3, 4, 5);
^
main.cpp:4:6: note: candidate: template<class A, class C, class ... Bs> void foo(A, Bs ..., C)
void foo(A, Bs..., C) { }
^~~
main.cpp:4:6: note: template argument deduction/substitution failed:
main.cpp:8:42: note: candidate expects 4 arguments, 5 provided
foo<int, int, int, int>(1, 2, 3, 4, 5);
^
To make things more interesting, calling with only four arguments is invalid for the first foo, and valid for the second.
It seems that in the first version of foo, C must be deduced, whereas in the second, C must be explicitly supplied.
What rules in the standard define this behaviour?

As is often the case, the answer came to me a few hours after I posted the question.
Consider the two versions of foo:
template<class A, class... Bs, class C>
void foo1(A, Bs..., C) { }
template<class A, class C, class... Bs>
void foo2(A, Bs..., C) { }
and the following call (assuming foo is foo1 or foo2):
foo<int,int,int,int>(1,2,3,4,5);
In the case of foo1, the template parameters are picked as follows:
A = int (explicitly provided)
Bs = {int,int,int} (explicitly provided)
C = int (deduced)
But in the case of foo2 they look like this:
A = int (explicitly provided)
C = int (explicitly provided)
Bs = {int,int} (explicitly provided)
Bs is in a non-deduced context ([temp.deduct.type]/5.7), so any further function arguments can not be used to extend the pack. As such, foo2 must have all it's template arguments explicitly provided.

Related

Why does my variadic template instantiation not work?

I am revisiting C++ after a long hiatus, and I would like to use templates to design the known "map" function -- the one which applies a function to every element of a collection.
Disregarding the fact my map doesn't return anything (a non-factor here), I have managed to implement what I wanted if the function passed to "map" does not need to accept additional arguments:
#include <iostream>
template <typename C, void fn(const typename C::value_type &)> void map(const C & c) {
for(auto i : c) {
fn(i);
}
}
struct some_container_type { /// Just some hastily put together iterable structure type
typedef int value_type;
value_type * a;
int n;
some_container_type(value_type * a, int n): a(a), n(n) { }
value_type * begin() const {
return a;
}
value_type * end() const {
return a + n;
}
};
void some_fn(const int & e) { /// A function used for testing the "map" function
std::cout << "`fn` called for " << e << std::endl;
}
int main() {
int a[] = { 5, 7, 12 };
const some_container_type sc(a, std::size(a));
map<some_container_type, some_fn>(sc);
}
However, I would like map to accept additional arguments to call fn with. I've tried to compile the modified variant of the program (container type definition was unchanged):
template <typename C, typename ... T, void fn(const typename C::value_type &, T ...)> void map(const C & c, T ... args) {
for(auto i : c) {
fn(i, args...);
}
}
void some_fn(const int & e, int a, float b, char c) {
std::cout << "`fn` called for " << e << std::endl;
}
int main() {
int a[] = { 5, 7, 12 };
const some_container_type sc(a, std::size(a));
map<some_container_type, int, float, char, some_fn>(sc, 1, 2.0f, '3');
}
But gcc -std=c++20 refuses to compile the modified program containing the above variant, aborting with:
<source>: In function 'int main()':
<source>:29:56: error: no matching function for call to 'map<some_container_type, int, float, char, some_fn>(const some_container_type&, int, int, int)'
29 | map<some_container_type, int, float, char, some_fn>(sc, 1, 2, 3);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
<source>:16:97: note: candidate: 'template<class C, class ... T, void (* fn)(const typename C::value_type&, T ...)> void map(const C&, T ...)'
16 | template <typename C, typename ... T, void fn(const typename C::value_type &, T ... args)> void map(const C & c, T ... args) {
| ^~~
<source>:16:97: note: template argument deduction/substitution failed:
<source>:29:56: error: type/value mismatch at argument 2 in template parameter list for 'template<class C, class ... T, void (* fn)(const typename C::value_type&, T ...)> void map(const C&, T ...)'
29 | map<some_container_type, int, float, char, some_fn>(sc, 1, 2, 3);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
<source>:29:56: note: expected a type, got 'some_fn'
Microsoft Visual C++ compiler (19.24.28314) gives a more descriptive error message:
error C3547: template parameter 'fn' cannot be used because it follows a template parameter pack and cannot be deduced from the function parameters of 'map'
Can someone explain if and how I can idiomatically accomplish for map to accept arbitrary arguments for forwarding these to fn?
I know I can pass fn to the map function as argument instead of specifying it as an argument to the template, but for reasons related to inlining and to better understand C++ templates, I'd like to retain fn a template rather than a function parameter.
I also don't want to use any libraries, including the standard library (what use of std I show in the examples above is only for clarifying the question). I know there are "functor" and "forward" somewhere in the libraries, but I suppose they too were written in C++, so I am curious if my problem can be solved without any libraries.
A simple way to fix this would be to deduce the non-type template parameter for the function, and reorder the template parameter list
template <typename C, auto fn, typename ... T>
void map(const C & c, T ... args) {
for(auto i : c) {
fn(i, args...);
}
}
and then call it like this
map<some_container_type, some_fn, int, float, char>(sc, 1, 2.0f, '3');
Here's a demo
You could also move fn to the beginning of the template parameter list.
template <auto fn, typename C, typename ... T>
void map(const C & c, T ... args) {
for(auto i : c) {
fn(i, args...);
}
}
Now since C and T can be deduced from the function arguments, this makes the call site much cleaner
map<some_fn>(sc, 1, 2.0f, '3');
Here's a demo

Template deduction, is this conforming?

It seems like this code is not correct as I get compiler errors for it. I'm trying to understand why:
template <class ... Ts>
struct type_list{};
template <class ... Ts, class T_, class ... Ts_>
auto foo(type_list<Ts...>, Ts&&..., T_&&t, Ts_&&...) {
return sizeof(T_);
}
int main() {
std::cerr << foo(type_list<int>{}, 5, 5.0, 3);
return 0;
}
clang produces the follow error:
example.cpp:16:16: error: no matching function for call to 'foo'
std::cerr << foo(type_list<int>{}, 5, 5.0, 3);
^~~
example.cpp:11:6: note: candidate template ignored: deduced conflicting types for parameter 'Ts'
(<int> vs. <>)
auto foo(type_list<Ts...>, Ts&&..., T_&&t, Ts_&&...) {
It seems to me that in my call Ts should be deduced to int (since that's the only way to make the first argument work out), and then everything else will be forced as a result. Why doesn't this work?
Clang deduces Ts from two conflicting sources: type_list<Ts...> and Ts&&.... Because you call foo with type_list<int>{}, Ts is first deduced as {int}. However, because the last argument Ts_&&... has the type of a parameter pack, it catches all that it can; in this case, the last two arguments (while T_&&t is passed the 5), which deduces Ts_ as {double, int} (and T_ as int). This leaves Ts with no argument, so it's deduced as {}.
Hence Clang's error message: Ts is deduced both as {} and {int} (the (<int> vs. <>) in the error message).
Note that GCC does things differently:
error: too few arguments to function 'auto foo(type_list, Ts&& ..., T_&&, Ts_&& ...) [with Ts = {int}; T_ = int; Ts_ = {double, int}]'
It tries to keep Ts deduced as {int} but still deduces Ts_ as {double, int}, and so there is no way to give the correct number of arguments.
To explain a bit further, consider the following:
template <class ... Ts>
struct type_list{};
template <class ... Ts>
auto foo(type_list<Ts...>, Ts...) {
return sizeof...(Ts);
}
int main() {
std::cerr << foo(type_list<int>{}, 5, 5.0, 3);
}
Clang correctly calls this out because the two deductions are conflicting:
note: candidate template ignored: deduced conflicting types for parameter 'Ts' (<int> vs. <int, double, int>)
Trouble is, you can't catch 2 of the arguments with another parameter pack, because it will either catch everything if placed after (which is your case), or nothing if placed before:
template <class ... Ts>
struct type_list{};
template <class... Ts, class... Ts_>
auto foo(type_list<Ts...>, Ts_..., Ts...) {
return sizeof...(Ts);
}
int main() {
std::cerr << foo(type_list<int>{}, 5, 5.0, 3);
}
Here, Clang produces the same error message because Ts is still deduced as both {int} from the type_list, and {int, double, int} by catching all the remaining arguments.
I think your only choices are to somehow deal with tuples or be more explicit with the call: foo<int>(type_list<int>{}, 5, 5.0, 3) (and you can probably remove the type_list in this case). That way, Ts isn't deduced anymore: you explicitly make it {int}.
C++ doesn't try every possibility and find the only one that is legal. It follows very specific rules, and if those rules don't work, it generates an error.
In general, a deduced ... pack must be last if it is deduced. Attempts to work around it will fail.
Here is a solution that avoids your problem:
template <class ... Ts, class...Us>
auto foo(type_list<Ts...>, Us&&...us) {
return [](Ts&&...ts, auto&& t, auto&&...) {
return sizeof(t);
}(std::forward<Us>(us)...);
}
int main() {
std::cerr << foo(type_list<int>{}, 5, 5.0, 3);
return 0;
}

Is templated alias conducting inner and outer parameter packs non-deduced context?

The problem came from here - I wanted to create an approach solving a little bit more general problem. Consider an example:
#include <utility>
template<class T, std::size_t>
using deduced = T;
template<std::size_t N, class = std::make_index_sequence<N>>
struct Foo;
template<std::size_t N, std::size_t... Is>
struct Foo<N, std::index_sequence<Is...>>{
template <class... Args>
void Bar(deduced<Args, Is>...)
{ }
};
int main() {
Foo<3> myfoo;
myfoo.Bar(true, 2.0f, 3); // OK
//myfoo.Bar(1, 2, 3, 4); // error
}
clang has no problem with compiling the code, gcc on the other hand shows following errors:
prog.cc: In function 'int main()':
prog.cc:18:27: error: no matching function for call to 'Foo<3ul>::Bar(bool, float, int)'
myfoo.Bar(true, 2.0f, 3); // valid
^
prog.cc:12:10: note: candidate: template<class ... Args> void Foo<N, std::integer_sequence<long unsigned int, Is ...> >::Bar(deduced<Args, Is>...) [with Args = {Args ...}; long unsigned int N = 3ul; long unsigned int ...Is = {0ul, 1ul, 2ul}]
void Bar(deduced<Args, Is>...)
^~~
prog.cc:12:10: note: template argument deduction/substitution failed:
prog.cc:18: confused by earlier errors, bailing out
[live demo]
What confuses me gcc does not have a problem with deduction when using the same alias but outer parameter pack isn't involved, e.g.:
void Bar(deduced<Args, 0>...)
So the question is - is it legal to combine parameter packs from outer and inner class with alias of this form to make compiler deduce one of the template parameters or is it gcc bug?
Edit (based on bogdan's comment):
The code causes trouble also to MSVC (2017 RC), but works in this form with EDG compiler, icc (in version 16 and 17) also seem to deal well with a code. It is also worth noting that similar code with class instead of alias (also considered as deduced context in some places example by bogdan) causes even more trouble to compilers - only clang seems to deal well with this version(?)

Default template argument for function ignored

template < class A, class B, class R = A >
void addMultiplyOperation( std::function< R ( const A&, const B& ) > func )
{
...
}
addMultiplyOperation< float, int >( []( float a, int b ) { return a * b; } );
This gives the compiler error:
In function 'int main(int, char**)':
error: no matching function for call to 'addMultiplyOperation(main(int, char**)::__lambda1)'
addMultiplyOperation< float, int >( []( float a, int b ) { return a * b; } );
^
note: candidate is:
note: template<class A, class B, class R> void addMultiplyOperation(std::function<R(const A&, const B&)>)
void addMultiplyOperation( std::function< R ( const A&, const B& ) > func )
^
note: template argument deduction/substitution failed:
note: 'main(int, char**)::__lambda1' is not derived from 'std::function<R(const float&, const int&)>'
addMultiplyOperation< float, int >( []( float a, int b ) { return a * b; } );
^
Despite having the R template argument default initialised to A, I have to provide the third argument in order for this to compile. Is there something else I have to do in order to use default template arguments?
I'm using g++ v4.8.1.
Despite having the R template argument default initialised to A, I have to provide the third argument in order for this to compile.
Actually, this has nothing to do with the fact that it's a default argument. The compiler can't deduce A and B either. Take a look at this simple example:
template<class A>
void f(function<void(A)> f) { }
int main() {
auto lambda = [](){};
f(lambda);
}
You'd think this would be super easy, and A should be deduced as void. But nope, it can't be done. When deducing template parameters, the compiler doesn't consider what constructors the parameter type would have for each possible combination of template parameters. It would be intractable to perform this sort of deduction in general.
For now, you'll just have to make addMultiplyOperation accept any type, and hope it's callable...
template<class Function>
void addMultiplyOperation(Function func) {
// ....
}
If necessary, there are ways to deduce the types of the arguments that the function object can accept, for example as described in this answer: Is it possible to figure out the parameter type and return type of a lambda?
This will lead to some nasty compilation errors if the object passed in is not actually callable, or takes the wrong number of arguments. For now I'm not sure whether there's a nice way to solve this. Concepts, coming in C++14, should alleviate some of these issues.

How to create a perfect forwarding constructor for a tuple-like variadic class

I am trying to create thing similar to tuple, but I have come across a problem for writing my constructor.
Here is the code:
#include <tuple>
template <typename... Ts>
struct B {
template <typename... ArgTypes>
explicit B(ArgTypes&&... args)
{
static_assert(sizeof...(Ts) == sizeof...(ArgTypes),
"Number of arguments does not match.");
}
};
struct MyType {
MyType() = delete;
MyType(int x, const char* y) {}
};
int main()
{
B <int, char> a{2, 'c'}; // works
B <int, bool, MyType, char> b{2, false, {4, "blub"}, 'c'}; // fails
std::tuple<int, bool, MyType, char> t{2, false, {4, "blub"}, 'c'}; // works
}
Now, this works ok if pass simple types as initializers, but it does not, if I try to pass arguments in a brace-enclosed initializer list for non-trivial object.
GCC-4.7 emits the following:
vararg_constr.cpp:21:67: error: no matching function for call to 'B<int, bool, MyType, char>::B(<brace-enclosed initializer list>)'
vararg_constr.cpp:21:67: note: candidates are:
vararg_constr.cpp:6:14: note: B<Ts>::B(ArgTypes&& ...) [with ArgTypes = {}; Ts = {int, bool, MyType, char}]
vararg_constr.cpp:6:14: note: candidate expects 0 arguments, 4 provided
Clang-3.1 the following:
vararg_constr.cpp:21:40: error: no matching constructor for initialization of
'B<int, bool, MyType, char>'
B <int, bool, MyType, char> b{2, false,{4, "blub"}, 'c'}; // fails
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
vararg_constr.cpp:6:14: note: candidate constructor not viable: requires 2
arguments, but 4 were provided
explicit B(ArgTypes&&... args)
Ok, now what makes me very, very curious is that it works for tuple! According to the Standard (20.4.2.1) it has a constructor, that looks pretty much like mine.
template <class... Types>
class tuple {
public:
// ...
template <class... UTypes>
explicit tuple(UTypes&&...);
// ...
};
When constructing the tuple object in the same way, it works!
Now I would like to know:
A) What the hell? Why is std::tuple so special, and why don't compilers deduce the correct number of arguments?
B) How can I make this work?
A) Why should the Compiler know, that {4, "blub"} is of type MyType and not tuple<int, const char*>?
B) Change ArgTypes to Ts in the constructor:
explicit B(Ts&&... args)
Tuple does also have the following constructor:
explicit constexpr tuple(const _Elements&... __elements);
EDIT: The point is, that the constructor with const& is called and not that with the R-Values. Consider the following:
template <typename... Ts>
struct B {
explicit B(const Ts&... elements) { std::cout << "A\n"; }
template<typename... As,
typename = typename std::enable_if<sizeof...(As) == sizeof...(Ts)>::type>
explicit B(As&&... elements) { std::cout << "B\n" ;}
};
int main()
{
MyType m {1, "blub"};
B<int, char> a{2, 'c'}; // prints B
B<bool, MyType, char> b{false, {4, "blub"}, 'c'}; // prints A
B<bool, MyType, MyType>c{false, {4, "blub"}, std::move(m)}; // prints A
}