Reputation: 157
On GCC/Clang The following code doesn't compile:
#include <iostream>
#include <type_traits>
using namespace std;
template<typename X, typename T, typename ...Ts>
void v(X& x, T& value, Ts&... args)
{
v(x, value);
v(x, args...);
}
template<typename X, typename T>
enable_if_t<is_integral_v<T>>
v(X& x, T& value) {
cout << value << endl;
}
template<typename X, typename T>
enable_if_t<is_floating_point_v<T>>
v(X& x, T& value) {
cout << value << endl;
}
int main() {
float f = 5.5;
int i = 2;
v(f, i, i);
}
Giving the error:
> prog.cc: In instantiation of 'void v(X&, T&, Ts& ...) [with X = float; T = int; Ts = {}]':
> prog.cc:8:3: required from 'void v(X&, T&, Ts& ...) [with X = float; T = int; Ts = {int}]'
> prog.cc:27:14: required from here
> prog.cc:9:3: error: no matching function for call to 'v(float&)'
> v(x, args...);
> ~^~~~~~~~~~~~
> prog.cc:6:6: note: candidate: template<class X, class T, class ... Ts> void v(X&, T&, Ts& ...)
> void v(X& x, T& value, Ts&... args)
> ^
> prog.cc:6:6: note: template argument deduction/substitution failed:
> prog.cc:9:3: note: candidate expects at least 2 arguments, 1 provided
> v(x, args...);
> ~^~~~~~~~~~~~
If the variadic function is defined after all the base function specializations are defined, the code compiles, but then you get this very undesirable constraint that makes code break with you include things in the wrong order.
Is there a way to add specializations to the variadic base function in a way that doesn't depend on definition order?
Upvotes: 0
Views: 120
Reputation: 66230
I see some problems in your code.
First and most impostant: you give the same name (v()
) to functions that are doing two completely different things; the first v()
call a printing function over the first argument and call recursively itself for the following arguments. Tho other v()
are printing (SFINAE selected) functions.
Suggestion: use different names; in the following example I've used foo()
for the recursive function and bar()
for the printing function.
Second: your first argument (X & x
) is unused.
Suggestion: remove it.
Third: your function are receiving not-const references but they don't modify the values.
Suggestion: receive the arguments as const references; so you can call also v(5.5f, 3L)
(you can't call this way with not-const references)
Following this suggestions, your printing functions become
template <typename T>
std::enable_if_t<std::is_integral<T>{}> bar (T const & value)
{ std::cout << "integral case: " << value << std::endl; }
template <typename T>
std::enable_if_t<std::is_floating_point<T>{}> bar (T const & value)
{ std::cout << "floating case: " << value << std::endl; }
and the recursive function (adding the ground case)
// ground case
void foo ()
{ }
// recursion case
template <typename T, typename ... Ts>
void foo (T const & value, Ts const & ... args)
{
bar(value);
foo(args...);
}
The following is a full working example
#include <iostream>
#include <type_traits>
template <typename T>
std::enable_if_t<std::is_integral<T>{}> bar (T const & value)
{ std::cout << "integral case: " << value << std::endl; }
template <typename T>
std::enable_if_t<std::is_floating_point<T>{}> bar (T const & value)
{ std::cout << "floating case: " << value << std::endl; }
// ground case
void foo ()
{ }
// recursion case
template <typename T, typename ... Ts>
void foo (T const & value, Ts const & ... args)
{
bar(value);
foo(args...);
}
int main ()
{
float f {5.5};
int i {2};
foo(f, i, i, 3L, 6.6f);
}
-- EDIT --
The OP say
The main problem is still left unsolved -
bar
s have to be defined beforefoo
.
If you accept that bar
s become static methods in a struct, I propose the following bar
struct
struct bar
{
template <typename T>
static std::enable_if_t<std::is_integral<T>{}> func (T const & value)
{ std::cout << "integral case: " << value << std::endl; }
template <typename T>
static std::enable_if_t<std::is_floating_point<T>{}> func (T const & value)
{ std::cout << "floating case: " << value << std::endl; }
};
and foo
become
// ground case
template <typename>
void foo ()
{ }
// recursion case
template <typename Bar, typename T, typename ... Ts>
void foo (T const & value, Ts const & ... args)
{
Bar::func(value);
foo<Bar>(args...);
}
and is called as follows
foo<bar>(f, i, i, 3L, 6.6f);
The following is a full working example where the bar
struct is defined after foo()
#include <iostream>
#include <type_traits>
// ground case
template <typename>
void foo ()
{ }
// recursion case
template <typename Bar, typename T, typename ... Ts>
void foo (T const & value, Ts const & ... args)
{
Bar::func(value);
foo<Bar>(args...);
}
struct bar
{
template <typename T>
static std::enable_if_t<std::is_integral<T>{}> func (T const & value)
{ std::cout << "integral case: " << value << std::endl; }
template <typename T>
static std::enable_if_t<std::is_floating_point<T>{}> func (T const & value)
{ std::cout << "floating case: " << value << std::endl; }
};
int main ()
{
float f {5.5};
int i {2};
foo<bar>(f, i, i, 3L, 6.6f);
}
Upvotes: 1