Reputation: 2843
This is the code I currently have:
class Foo
{
public:
template<typename T, typename... Args>
void Function(T t1, Args... args){
// Definition
}
private:
template<typename T>
void Function(T t1){
// Definition
}
};
#include "header.h"
int main()
{
Foo foo;
foo.Function(1, 2, 3, 4, 5);
return 0;
}
Works just fine. When I try to separate the definition to source.cpp, the gcc starts complaining. I know I have to specialize the templates in order to separate the definition, so I tried adding the code below to the header file:
template<>
void Foo::Function<int, int...>(int t1, int... args);
template<>
void Foo::Function<int>(int);
but without success. What am I missing
edit: gcc error messages:
header.h:15:28: error: expansion pattern ‘int’ contains no argument packs void Foo::Function(int t1, int... args);
header.h:15:48: error: expansion pattern ‘int’ contains no argument packs void Foo::Function(int t1, int... args);
Upvotes: 1
Views: 284
Reputation: 37597
There is better way to do it.
First of all it looks like you want to force same type of all arguments (this is done by std::initializer_list
in accepted answer). This can be foreced by providing extra explicit argument:
class Foo
{
public:
template<typename T, typename... Args>
void Function(T t1, T t2, Args... args)
{
LOG;
this->Function(t1);
this->Function(t2, args...);
}
private:
template<typename T>
void Function(T t1)
{
LOG << VAR(t1);
}
};
template<>
void Foo::Function<int>(int x)
{
LOG << " Spec" << VAR(x);
}
As you can see it is enough if you provide specialization of method for single argument.
Upvotes: 0
Reputation: 10740
You can't use int...
as a parameter pack, and so this doesn't work. In addition, to separate the source from the definition, you have to fully specify the template, so int...
wouldn't work even if that syntax were allowed.
1. Make Function
accept an initializer list.
We can write function so that it accepts an initializer list of int
s:
#include <initializer_list>
class Foo {
public:
void Function(int t1, std::initializer_list<int> t2);
};
void Foo::Function(int t1, std::initializer_list<int> t2) {
for(int i : t2) {
// stuff
}
}
Now, you can call Function
pretty easily, and it's not even templated:
Foo f;
f.Function(10, {1, 2, 3, 4, 5});
If there are other places you're using templates, you can expand a parameter pack directly into the initializer list:
template<class... Args>
void invoke_foo(Foo& f, int first, Args... rest) {
f.Function(first, {rest...});
}
2. Use SFINAE to disable all non-int overloads. We can disable all overloads of Foo::Function
that don't only accept int
s
#include <type_traits>
class Foo {
public:
// Requires C++17 for std::conjunction
// You could write your own std::conjunction too if you'd prefer
template<class... Args>
auto Function(int t1, Args... t2)
-> std::enable_if_t<std::conjunction<std::is_same<Args, int>...>::value>
{
// stuff
}
};
The downside to this is that non-integral values won't automatically be converted to int
.
Upvotes: 2