I needed to make a function that takes a function pointer with variable arguments and some fixed arguments after them and could not make it work on Visual Studio 2013. I assumed that maybe Visual Studio 2013 was missing something which is often a case and made a minimal example that did what I needed and tried it against gcc and clang. And I got totally different results on all three compilers. So the questions which I'd like to resolve are:
The example:
#include <iostream>
struct foo
{
void work(int first, int second, int third)
{
std::cout << "0: " << first << ",1: " << second << ",2: " << third << std::endl;
}
void work_with_double(double first, int second, int third, int fourth)
{
std::cout << "0: " << first << ",1: " << second << ",2: " << third << ",3: " << fourth << std::endl;
}
};
template<typename ... argument_types>
void invoke_foo(foo* instance, int first, int second, int third, void (foo::*method)(argument_types ... arguments, int, int, int), argument_types ... arguments)
{
(instance->*method)(arguments ..., first, second, third);
}
int main(int argc, char** argv)
{
foo instance;
invoke_foo(&instance, 1, 2, 3, &foo::work); // gcc ok, clang err, msvc 2013 err
invoke_foo<>(&instance, 1, 2, 3, &foo::work); // gcc ok, clang err, msvc 2013 err
invoke_foo(&instance, 1, 2, 3, &foo::work_with_double, 1.0); // gcc err, clang ok, msvc 2013 err
invoke_foo<double>(&instance, 1, 2, 3, &foo::work_with_double, 1.0); // gcc err, clang err, msvc 2013 ok
return 0;
}
Modified snippet that makes Visual Studio 2015 (w/o updates) crash
If invoke_foo
is made as a member function of an object, Visual Studio 2015 crashes.
#include <iostream>
#include <memory>
struct foo
{
void work(int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eight)
{
std::cout << "0: " << first << ",1: " << second << ",2: " << third << std::endl;
}
void work_with_double(double firstExtra, int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eight)
{
std::cout << "0: " << first << ",1: " << second << ",2: " << third << ",3: " << fourth << std::endl;
}
};
struct bar
{
};
struct wrapper
{
template <typename T> struct non_deduced { using type = T; };
template <typename T> using non_deduced_t = typename non_deduced<T>::type;
template<typename ... argument_types>
std::shared_ptr<bar> invoke_foo(int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eight, void (foo::*method)(non_deduced_t<argument_types>... arguments, int, int, int, int, int, int, int, int), argument_types ... arguments)
{
(foo_.get()->*method)(arguments ..., first, second, third, fourth, fifth, sixth, seventh, eight);
return nullptr;
}
std::unique_ptr<foo> foo_ = std::move(std::unique_ptr<foo>(new foo));
};
int main(int argc, char** argv)
{
wrapper instance;
instance.invoke_foo(1, 2, 3, 4, 5, 6, 7, 8, &foo::work);
instance.invoke_foo(1, 2, 3, 4, 5, 6, 7, 8, &foo::work_with_double, 1.0);
}
The problem in each case is that the compiler is trying to infer argument_types
from the method
argument, which is illegal as variadic template parameters can only be inferred when they are at the end of an argument list.
void (foo::*method)(argument_types ... arguments, int, int, int)
^^^^^^^^^^^^^^^^^^ can't infer here
^^^^^^^^^^^^^^^ because of these
The workaround is to protect argument_types
from being deduced in this context, using a helper like identity
:
template<class T> struct identity { using type = T; };
template<class T> using identity_t = typename identity<T>::type;
// ...
template<typename ... argument_types>
void invoke_foo(foo* instance, int first, int second, int third,
void (foo::*method)(identity_t<argument_types> ... arguments, int, int, int), argument_types ... arguments)
// ^^^^^^^^^^^ fix here
Is this a bug in your code, or in the compilers? Actually, it's a bug in the compilers (yes, all of them); the question is whether a parameter pack appearing within a function type and not at the end of an argument list is a non-deduced context. The relevant part of the standard is [temp.deduct.type], which states:
5 - The non-deduced contexts are: [...]
- A function parameter pack that does not occur at the end of the parameter-declaration-list.
6 - When a type name is specified in a way that includes a non-deduced context, all of the types that comprise that type name are also non-deduced. However, a compound type can include both deduced and non-deduced types.
Here, argument_types
is in a non-deduced context when deducing the type of method
, but a deduced context when deducing the types for the trailing arguments of invoke_foo
.
Another compiler you could test is ICC (Intel C++ Compiler); ICC rejects the first two forms and accepts the last two, the exact opposite to gcc. The reason that compilers can be so different in their behavior is that dealing with this kind of code is essentially a matter of error handling, specifically recognising when template parameters appear in non-deduced contexts and using the types deduced elsewhere instead. The compilers are (each in their own way) recognising that argument_types
cannot be deduced within method
, but failing to realise or accept that it can be deduced elsewhere.
Specifically, it appears that:
argument_types
from method
, it must be empty;argument_types
is deduced to be empty or explicitly specified, this must be an error;argument_types
override the failure to deduce it, but is OK if it is explicitly specified;argument_types
is deduced to be empty, this must be an error.