Parameter Pack Matching Rules with Multiple Packs - c++

I am trying to write a function which takes another function, using parameter packs and some standard matching rules. As an example:
template <typename... TListElems, typename... TVectorElems>
void goal(void (*fn)(std::list<TListElems>..., std::vector<TVectorElems>...));
In order to disambiguate TListElems and TVectorElems, I added some std::tuple<T...>* so a caller can be explicit:
template <typename... TListElems, typename... TVectorElems>
void foo(std::tuple<TListElems...>*,
std::tuple<TVectorElems...>*,
void (*)(std::list<TListElems>..., std::vector<TVectorElems>...))
{
// blah blah blah
}
void bar(std::list<int>, std::list<unsigned>, std::vector<float>, std::vector<double>)
{
// blah blah blah
}
int main()
{
foo((std::tuple<int, unsigned>*) nullptr,
(std::tuple<float, double>*) nullptr,
&bar);
}
Clang happily compiles this in the way I would expect, while g++ (7.2.1) gives the compilation error:
matching.cpp: In function ‘int main()’:
matching.cpp:20:13: error: no matching function for call to ‘foo(std::tuple<int, unsigned int>*, std::tuple<float, double>*, void (*)(std::list<int>, std::list<unsigned int>, std::vector<float>, std::vector<double>))’
&bar);
^
matching.cpp:6:6: note: candidate: template<class ... TListElems, class ... TVectorElems> void foo(std::tuple<_Tps ...>*, std::tuple<_Elements ...>*, void (*)(std::list<TListElems>..., std::vector<TVectorElems>...))
void foo(std::tuple<TListElems...>*,
^~~
matching.cpp:6:6: note: template argument deduction/substitution failed:
matching.cpp:20:13: note: mismatched types ‘std::vector<TVectorElems>’ and ‘std::list<int>’
&bar);
^
In main, I would expect the call to foo to deduce TListElems as <int, unsigned> and TVectorElems as <float, double>, leading fn to be of type void (*)(std::list<int>, std::list<unsigned>, std::vector<float>, std::vector<double>) (the way things operate when there is only one pack or if I had manually written the overload).
§14.8.2.5/10 is the closest the Standard comes to explicitly preventing the foo example from working:
[Note: A function parameter pack can only occur at the end of a parameter-declaration-list (8.3.5). -end note]
The std::list<TListElems>... bit of fn seems like it would violate this note, but that's not entirely clear.
The question is: Who is right? GCC, Clang, or something else?

I think clang is right here.
In void (*)(std::list<TListElems>..., std::vector<TVectorElems>...), TListElems... is a non-deduced context, which makes TVectorElems... also a non-deduced context. But both parameter packs are deducible from the two tuple pointer arguments, and it's expected to be able to just use that deduction result here too.
I filed gcc bug 83542.

You may make happy both compilers with non deducible type:
template <typename T>
struct non_deducible {
using type = T;
};
template <typename T> using non_deducible_t = typename non_deducible<T>::type;
template <typename... TListElems, typename... TVectorElems>
void foo(std::tuple<TListElems...>*,
std::tuple<TVectorElems...>*,
void (*)(non_deducible_t<std::list<TListElems>>...,
non_deducible_t<std::vector<TVectorElems>>...))
{
// blah blah blah
}
Demo

Related

Default template argument deducing to the wrong type in a conditional

I have the following testcase:
#include <type_traits>
template <typename Ty_>
using conditional_ref =
typename std::conditional<std::is_fundamental<Ty_>::value, Ty_, Ty_ const&>::type;
template <typename Ty_, typename Ty2_ = conditional_ref<Ty_>>
inline void f(Ty2_ arg1) {
static_assert(std::is_same<Ty2_, conditional_ref<Ty_>>::value, "uh-oh!");
}
int main() {
struct A {};
A a{};
double b{};
//f(a); // Cannot deduce parameter (expected)
//f(b); // Cannot deduce parameter (expected)
f<decltype(a)>(a); // uh-oh!
f<decltype(b)>(b); // OK
f<decltype(a), conditional_ref<decltype(a)>>(a); // OK
f<decltype(b), conditional_ref<decltype(b)>>(b); // OK
return 0;
}
In this, f<decltype(a)> deduces to f<A, A> instead of the expected f<A, A const&>.
I have tried in clang-10, gcc-9, and Visual Studio 2019; all of these give the same result, so I think I'm doing something wrong.
Example output from clang-10:
$ clang-10 test.cpp
test.cpp:9:3: error: static_assert failed due to requirement 'std::is_same<A, const A &>::value' "uh-oh!"
static_assert(std::is_same<Ty2_, conditional_ref<Ty_>>::value, "uh-oh!");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:19:3: note: in instantiation of function template specialization 'f<A, A>' requested here
f<decltype(a)>(a); // uh-oh!
^
1 error generated.
Why is the template deduction not what I expect here?
How do I rewrite f() so that it accepts a Ty_ when passed a fundamental type, and a Ty_ const& otherwise? Preferably while only having to pass the type of the argument once, like f<decltype(a)>(a).
Ty2_ is deducible, so default is not used at all. you might use directly:
template <typename Ty_>
void f(conditional_ref<Ty_> arg1) {
static_assert(std::is_same<decltype(arg1), conditional_ref<Ty_>>::value, "uh-oh!");
}
Demo
Type of Ty2_ is deduced by provided argument of function call. You can disable this deduction by
void f(std::type_identity_t<Ty2_> arg1)
then default template parameter type will be used.

Why do my SFINAE expressions no longer work with GCC 8.2?

I recently upgraded GCC to 8.2, and most of my SFINAE expressions have stopped working.
The following is somewhat simplified, but demonstrates the problem:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename std::enable_if<
std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename std::enable_if<
!std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
C++ (gcc) – Try It Online
C++ (clang) – Try It Online
Older versions of GCC (unfortunately I don't remember the exact version I had installed previously) as well as Clang compile the above code just fine, but GCC 8.2 gives an error stating:
: In function 'int main()':
:29:19: error: call of overloaded 'test()' is ambiguous
c.test();
^
:12:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value>::type ... = {}]'
void test() {
^~~~
:22:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value)>::type ... = {}]'
void test() {
^~~~
:30:25: error: call of overloaded 'test()' is ambiguous
c.test();
^
:12:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value>::type ... = {}]'
void test() {
^~~~
:22:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value)>::type ... = {}]'
void test() {
As is usually the case when different compilers and compiler versions handle the same code differently I assume I am invoking undefined behavior. What does the standard have to say about the above code? What am I doing wrong?
Note: The question is not for ways to fix this, there are several that come to mind. The question is why this doesn't work with GCC 8 - is it undefined by the standard, or is it a compiler bug?
Note 2: Since everyone was jumping on the default void type of std::enable_if, I've changed the question to use int instead. The problem remains.
Note 3: GCC bug report created
This is my take on it. In short, clang is right and gcc has a regression.
We have according to [temp.deduct]p7:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. [...]
This means that the substitution has to happen whether or not the pack is empty or not. Because we are still in the immediate context, this is SFINAE-able.
Next we have that a variadic parameter is indeed considered an actual template parameter; from [temp.variadic]p1
A template parameter pack is a template parameter that accepts zero or more template arguments.
and [temp.param]p2 says which non-type template parameters are allowed:
A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
a type that is literal, has strong structural equality ([class.compare.default]), has no mutable or volatile subobjects, and in which if there is a defaulted member operator<=>, then it is declared public,
an lvalue reference type,
a type that contains a placeholder type ([dcl.spec.auto]), or
a placeholder for a deduced class type ([dcl.type.class.deduct]).
Note that void doesn't fit the bill, your code (as posted) is ill-formed.
I am not a language lawyer, but cannot the following quote be somehow connected to the problem?
[temp.deduct.type/9]: If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi.
It seems to me that since there is no remaining argument in the template argument list, then there no comparison of the pattern (which contains enable_if). If there is no comparison, then there is also no deduction and substitution occurs after deduction I believe. Consequently, if there is no substitution, no SFINAE is applied.
Please correct me if I am wrong. I am not sure whether this particular paragraph applies here, but there are more similar rules regarding pack expansion in [temp.deduct]. Also, this discussion can help someone more experienced to resolve the whole issue: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A.
Partial answer: use typename = typename enable_if<...>, T=0 with different Ts:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename = typename std::enable_if_t<
std::is_const<typename std::remove_reference<U>::type>::value
>, int = 0
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename = typename std::enable_if_t<
!std::is_const<typename std::remove_reference<U>::type>::value
>, char = 0
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
(demo)
Still trying to figure out what the heck does std::enable_if<...>::type... mean knowing the default type is void.

C++ Passing std::function object to variadic template

I want to pass a callable (std::function object) into a class Foo. The callable refers to a member method of another class which has arbitrary arguments, hence the Foo must be a variadic template. Consider this code:
struct Bar {
void MemberFunction(int x) {}
};
template<typename ...Args>
class Foo {
public:
Foo(std::function<void(Bar*, Args...)> f) {}
};
int main() {
Foo<int> m1(&Bar::MemberFunction);
return 0;
}
This compiles fine. Now I want to write a factory function MakeFoo() which returns a unique_ptr to a Foo object:
template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
return std::make_unique<Foo<Args...>>(f);
}
Using this function by calling
auto m2 = MakeFoo<int>(&Bar::MemberFunction);
in main, gives me the following compiler errors:
functional.cc: In function ‘int main()’:
functional.cc:21:50: error: no matching function for call to ‘MakeFoo(void (Bar::*)(int))’
auto m2 = MakeFoo<int>(&Bar::MemberFunction);
^
functional.cc:15:35: note: candidate: template<class ... Args> std::unique_ptr<Foo<Args ...> > MakeFoo(std::function<void(Bar*, Args ...)>)
std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
^
functional.cc:15:35: note: template argument deduction/substitution failed:
functional.cc:21:50: note: mismatched types ‘std::function<void(Bar*, Args ...)>’ and ‘void (Bar::*)(int)’
auto m2 = MakeFoo<int>(&Bar::MemberFunction);
It seems to me, that when I call the constructor of Foo, the compiler happily converts the function pointer &Bar::MemberFunction to a std::function object. But when I pass the same argument to the factory function, it complains. Moreover, this problem only seems to occur, when Foo and MakeFoo are variadic templates. For a fixed number of template parameters it works fine.
Can somebody explain this to me?
Why doesn't it work without explicit <int>?
Prior to C++17, template type deduction is pure pattern matching.
std::function<void(Foo*)> can store a member function pointer of type void(Foo::*)(), but a void(Foo::*)() is not a std::function of any kind.
MakeFoo takes its argument, and pattern matches std::function<void(Bar*, Args...)>. As its argument is not a std::function, this pattern matching fails.
In your other case, you had fixed Args..., and all it had to do was convert to a std::function<void(Bar*, Args...)>. And there is no problem.
What can be converted to is different than what can be deduced. There are a myriad of types of std::function a given member function could be converted to. For example:
struct Foo {
void set( double );
};
std::function< void(Foo*, int) > hello = &Foo::set;
std::function< void(Foo*, double) > or_this = &Foo::set;
std::function< void(Foo*, char) > why_not_this = &Foo::set;
In this case there is ambiguity; in the general case, the set of template arguments that could be used to construct some arbitrary template type from an argument requires inverting a turing-complete computation, which involves solving Halt.
Now, C++17 added deduction guides. They permit:
std::function f = &Foo::set;
and f deduces the signature for you.
In C++17, deduction doesn't guides don't kick in here; they may elsewhere, or later on.
Why doesn't it work with explicit <int>?
Because it still tries to pattern match and determine what the rest of Args... are.
If you changed MakeFoo to
template<class T>
std::unique_ptr<Foo<T>> MakeFoo(std::function<void(Bar*, T)> f) {
return std::make_unique<Foo<T>>(f);
}
suddenly your code compiles. You pass it int, there is no deduction to do, and you win.
But when you have
template<class...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(std::function<void(Bar*, Args...)> f) {
return std::make_unique<Foo<T>>(f);
}
the compiler sees <int> and says "ok, so Args... starts with int. What comes next?".
And it tries to pattern match.
And it fails.
How can you fix it?
template<class T>struct tag_t{using type=T; constexpr tag_t(){}};
template<class T>using block_deduction=typename tag_t<T>::type;
template<class...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(
block_deduction<std::function<void(Bar*, Args...)>> f
) {
return std::make_unique<Foo<T>>(f);
}
now I have told the compiler not to deduce using the first argument.
With nothing to deduce, it is satisfied that Args... is just int, and... it now works.
The compiler cannot deduce the template arguments for std::function from a different type, such as member function pointer. Even though a std::function can be constructed from on object of that type, to consider the constructor the template arguments of std::function must be first known.
To help it deduce, add another overload:
template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo(void(Bar::*f)(Args...)) {
return std::make_unique<Foo<Args...>>(f);
}
MakeFoo<int>( whatever );
is equivalent to invoking the hypothetical
template<typename ...Tail>
std::unique_ptr<Foo<int,Tail...>> MakeFoo( std::function<void(Bar*,int,Tail...)> f) {
return std::make_unique<Foo<int,Tail...>>(f);
}
clearly, in no way the compiler can deduce that Tail is empty given a void(Bar::*)(int)
IMO, the most correct fix ( given the required usage ) is to make the args non deduced:
template< typename T >
struct nondeduced { using type = T; };
template<typename ...Args>
std::unique_ptr<Foo<Args...>> MakeFoo( std::function<void(Bar*, typename nondeduced<Args>::type... )> f ) {

pre-typedef'ing a variadic-function-pointer argument

I have a function foo that takes a variadic function pointer as its argument.
I would like to use "using" to define the argument's type prior to the function declaration.
template <typename ... vARGS>
using TFuncType = void(*)(vARGS ... V_args);
template <typename ... vARGS>
void foo(TFuncType<vARGS ...> funcptr) {}
void bar(int i) {}
int main() {
foo(&bar); // This line fails to compile.
}
This doesn't compile. The error (via clang using c++1z) is:
/make/proj/test/variadic-funcparam-deduce2.cpp:39:5: error: no matching function for call to 'foo'
foo(&bar);
^~~
/make/proj/test/variadic-funcparam-deduce2.cpp:33:36: note: candidate template ignored: substitution failure [with vARGS = int]
template <typename ... vARGS> void foo(TFuncType<vARGS ...> funcptr) {}
Why is the "int" substitution failing?
I can successfully compile if I explicitly write the type inside foo():
template <typename ... vARGS>
void foo(void(*funcptr)(vARGS ... V_args)) {}
But I cannot get the initial ("using") version to work even when explicitly specifying the template parameters, and using a pre-casted TFuncType<int> for the argument, i.e.:
int main() {
TF_call<int> fptr = &bar; // This line is OK.
foo<int>(fptr);
}
Does anyone know what's up here?
Is there something strange about using typedef'd ("using") variadics and/or function pointers that I'm missing?
I believe this may be related to the following text which I copied from this answer that itself takes from the C++ standard in 14.5.7 [temp.alias] paragraph 2:
When a template-id refers to the specialization of an alias template,
it is equivalent to the associated type obtained by substitution of
its template-arguments for the template-parameters in the type-id of
the alias template. [ Note: An alias template name is never deduced. —
end note ]
If I'm interpreting that right, it means that GCC accepting the code is actually non-conforming.

Why can't I manually provide the template arguments?

I have a variadic template function f. This compiles fine (using g++ -std=c++11 and possibly using c++0x):
#include <tuple>
template<int ...>
struct seq { };
template <typename ...T, int ...S>
void f(std::tuple<T...> arg, seq<S...> s) {
// ... do stuff
}
int main() {
f<int>(std::tuple<int>(10), seq<0>());
return 0;
}
The compiler automatically fills in the int ...S that works.
However, I can't seem to manually provide the integer arguments:
int main() {
f<int, 0>(std::tuple<int>(10), seq<0>());
return 0;
}
Output:
/tmp/t.cpp: In function ‘int main()’: /tmp/t.cpp:12:42: error: no
matching function for call to ‘f(std::tuple, seq<0>)’
/tmp/t.cpp:12:42: note: candidate is: /tmp/t.cpp:7:6: note:
template void f(std::tuple<_TElements ...>,
seq) /tmp/t.cpp:7:6: note: template argument
deduction/substitution failed:
I believe I've read that technically there should only be one variadic template parameter pack provided to a template function (and in the first case, it is completely determined by context), so that explains it(?).
For debugging, is there a way in GCC to output the expansion used for ...S to stderr or stdout? It would be very useful for debugging things like this when they don't compile at first.
I know no way to specify two template argument packs manually. Since a template pack may contain arbitrarily many arguments, there is no way for the compiler to know when you meant the first one to stop and the second one to start. Automatic or partially automatic deduction seems to work, though, but I am not sure if this is just some generousity of g++...
I am not sure what you are actually trying to do, but I bet that you do not need both template packs at the same time. You might introduce one layer of indirection, e.g.,
template <typename ... Tuple_Elements>
void do_something_with_single_value(std::tuple<Tuple_Elements...> arg, int s) {
// ... do stuff
}
template <typename Tuple_Type, int ...S>
void f(Tuple_Type arg, seq<S...> s) {
// ... do stuff which needs all S at the same time
// ... call do_something_with_single_value in compile time loop to access tuple elements
}
Perhaps your signature is a hint that your function has too many responsibilities. Try to create smaller functions with clear responsibilities.
There is a way to output the arguments deduced for T and S, but only if the compiler can determine a match. For this, you would need to provoke an error at compile time:
template <typename ...T, int ...S>
void f(std::tuple<T...> arg, seq<S...> s) {
static_assert(std::tuple_size<std::tuple<T...>>::value < 0, "Provoked error message");
// ... do stuff
}
This would generate the following output in your working example:
stack.cpp: In function ‘void f(std::tuple<_Elements ...>, seq<S ...>) [with T = {int}, int ...S = {0}]’:
stack.cpp:15:34: instantiated from here
stack.cpp:10:2: error: static assertion failed: "Provoked error message"
For getting stderr for types and values in situations like this I, personally, find it best to force a compile error for the situation you're interested in.
So in this situation we want to know exactly what the type and integer packs for the first example where it compiles correctly. If we cause a compile fail before instantiation of the function then the compile error will refer to the function without the info we want:
template <class ... T, int ... S>
void f (std::tuple<T...> arg, seq<S...> s)
{
foo
}
In function 'void f(std::tuple<_Elements ...>, seq<S ...>)':
error: 'foo' was not declared in this scope
demo
So now let's fail compilation during instantiation. This means that we'll have to write something that could make sense for some types of T..., but not for our instantiation. I usually write a function like force_fail to make this easy to repeat:
template <class T, class ...>
int force_compile_fail ()
{
return T::force_compile_fail;
}
template <class ... T, int ... S>
void f (std::tuple<T...> arg, seq<S...> s)
{
force_compile_fail<T...>();
}
In instantiation of 'int force_compile_fail() [with T = int;
= {}]':
required from 'void f(std::tuple<_Elements ...>, seq<S ...>) [with T = {int}; int ...S = {0}]'
required from here
error: 'force_compile_fail' is not a member of 'int'
return T::force_compile_fail;
demo