Template specialization in presence of variadic templates [duplicate] - c++

Suppose I have a class with the following signature:
template <typename T, typename... Args>
class A;
But how this class behaves should depend on some other parameter, let's say it's the value of T::value:
template <typename T, typename... Args, typename Enable>
class A;
template <typename T, typename... Args, typename = typename std::enable_if<T::value>::type>
class A
{
// do something
};
template <typename T, typename... Args, typename = typename std::enable_if<!T::value>::type>
class A
{
// do something else
};
int main() { return 0; }
However, this program gives the following error:
prog.cpp:6:11: error: parameter pack ‘Args’ must be at the end of the
template parameter list
class A;
I have struggled to find a good source of information on the use of enable_if to select classes with variadic templates. The only question I could find is this one:
How to use std::enable_if with variadic template
But despite the name, this question and its answers aren't much help. If someone could provide or link a guide on how this should be approached and why that would be appreciated.

First of all, what you're trying is writing multiple definitions of a class template. That's not allowed because it violates One definition rule. If you want to do conditional enabling with classes, you need specializations. Also, the compiler error message already told you, you can't have a variadic parameter pack in the middle of a parameter list.
One way to do it would be:
namespace detail {
template<typename T, typename Enable, typename... Args>
class A_impl;
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<T::value>::type, Args...> {
// code here
};
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<!T::value>::type, Args...> {
// code here
};
}
template<typename T, typename...Args>
class A : public detail::A_impl<T, void, Args...> {};
Jonathan's way is also perfectly fine if the condition is really a bool, but it might not be useful if you wish to add more specializations that each depend on several conditons.

It looks as though for your purposes you don't need to enable/disable the class, you just need a partial specialization:
template <typename T, bool B = T::value, typename... Args>
class A;
template <typename T, typename... Args>
class A<T, true, Args...>;
template <typename T, typename... Args>
class A<T, false, Args...>;

Related

How to use ... in a class template specialization?

This is an example with ... in the middle position of a template, however I cant understand what it means.
template <typename T, T f> class Invalid;
template <typename T1, typename... Args, T1 (*f)(Args...)>
class Invalid<T1 (*)(Args...), f>
{};
The following also works for me and is easy to comprehend.
template <typename... Args> class Invalid;
template <typename T1, typename... Args>
class Invalid<T1 (*)(Args...), T1>
{};
Why would I use the first one instead of the second?

Generalizing is_detected with variadic function parameters

I am trying to modify the is_detected idiom to allow passing variadic arguments to it. I need this since some of my detected member functions will have user provided arguments.
So far, this is what I got working. You give the extra args to is_detected_args_v, and in theory, the template specialization would kick in and compile correctly. Thus giving std::true_type.
#include <type_traits>
#include <cstdio>
// slightly modified (and simplified) is_detected
template <template <class, class...> class Op, class T, class = void, class...>
struct is_detected_args : std::false_type {};
template <template <class, class...> class Op, class T, class... Args>
struct is_detected_args<Op, T, std::void_t<Op<T, Args...>>, Args...>
: std::true_type {};
template <template <class, class...> class Op, class T, class... Args>
inline constexpr bool is_detected_args_v
= is_detected_args<Op, T, Args...>::value;
// has_func, checks the function starts with int, and then Args&...
template <class T, class... Args>
using has_func = decltype(std::declval<T>().func(
std::declval<int>(), std::declval<Args&>()...));
// has the func
struct obj {
void func(int, double&, double&) {
printf("potato\n");
}
};
int main(int, char**) {
obj o;
if constexpr(is_detected_args_v<has_func, obj, double, double>) {
double d = 0;
double d2 = 42;
o.func(42, d, d2);
}
}
You can run the example here (tested on all 3 compilers) : https://wandbox.org/permlink/ttCmWSVl1XVZjty7
The problem is, the specialization is never chosen and the conditional is always false. My question is two-folds.
Is this even possible?
Why doesn't is_detected get specialized?
Thx
The main issue here is misunderstanding what void_t does. As a refresher, see how does void_t work?. The key idea is that the primary template has a void parameter and the specialization has some complex thing that you want to check wrapped in void_t so that it matches the primary template's parameter. That isn't happening in your example.
We can fix it in two easy steps. First, you have this type T along with Args... There isn't actually any reason to split this up, and it's easier to look at if we don't have extraneous parameters. So here's your attempt just reduced (I also gave a name to the parameter which is supposed to be void):
template <template <class...> class Op, class AlwaysVoid, class...>
struct is_detected_args : std::false_type {};
template <template <class...> class Op, class... Args>
struct is_detected_args<Op, std::void_t<Op<Args...>>, Args...>
: std::true_type {};
template <template <class...> class Op, class... Args>
inline constexpr bool is_detected_args_v = is_detected_args<Op, Args...>::value;
Now it should be easier to see what's missing: the void parameter! You're not passing in a void and you need to. That's an easy fix though:
template <template <class...> class Op, class... Args>
inline constexpr bool is_detected_args_v = is_detected_args<Op, void, Args...>::value;
// ~~~~~
And now it works as expected.
Cppreference also provides a complete implementation of is_detected if you want to look at that too.

Compile time detection of functions

I'm working on a little compile time helper that let's me determine if a function (for now: no namespaces or class member functions) with a specific signature exists (e.g. __builtin_pop_count which is widely spread, but not guaranteed to be available on any platform).
For a fixed number of arguments, this is easily done:
template <typename ReturnType, typename ArgumentType, typename = void>
struct Exists : std::false_type // Base case
{
};
template <typename T>
using void_t = void;
template <typename T>
using return_t = decltype(foo(std::declval<T>())); // here it is hidden: foo, although this symbol is never declared!
// specialization (compiler has to pick this one if no substitution failure in return_t
template <typename ReturnType, typename ArgumentType>
struct Exists<ReturnType, ArgumentType, void_t<return_t<ArgumentType>>>
: std::is_same<return_t<ArgumentType>, ReturnType> // check the return type
{
};
static_assert(!Exists<void, int>::value, "");
static_assert(!Exists<void, void>::value, "");
static_assert(!Exists<void, char*>::value, "");
static_assert(!Exists<int, void>::value, "");
static_assert(!Exists<int, int>::value, "");
static_assert(!Exists<int, char*>::value, "");
This compiles fine. Adding the function void foo(int) negates the first assertion, but leaves the rest intact.
Now I would like to extend this helper to support an arbitrary number of argument types.
However,
template <typename ReturnType, typename... ArgumentTypes, typename = void>
cannot work, because typename... must be at the end of the list,
template <typename ReturnType, typename = void, typename... ArgumentTypes>
on the other hand requires the following ArgumentTypes to have a default type which also is not possible.
How can I circumvent this? Can a std::tuple<ArgumentTypes...> help in any way?
You guessed it.
Use a pack template, and "simplify" a bit:
template <typename...> struct pack {}; // To be potentially introduced in C++1Z
template <typename, typename, typename=void>
struct Exists_impl : std::false_type {};
template <typename R, typename... Args>
struct Exists_impl<R, pack<Args...>,
std::enable_if_t<std::is_same<decltype(foo(std::declval<Args>()...)), R>::value>>
: std::true_type {};
template <typename R, typename... Args>
using Exists = Exists_impl<R, pack<Args...>>;
Demo.
Note that this template will never be able to find functions like void(int) via ADL, as the set of associated namespaces is empty in such cases. Those functions must be declared at the point of definition.
Also it might be feasible to use is_convertible instead of is_same for the check of the return type, depending on the use-case.

How do I enable_if a class with variadic template arguments?

Suppose I have a class with the following signature:
template <typename T, typename... Args>
class A;
But how this class behaves should depend on some other parameter, let's say it's the value of T::value:
template <typename T, typename... Args, typename Enable>
class A;
template <typename T, typename... Args, typename = typename std::enable_if<T::value>::type>
class A
{
// do something
};
template <typename T, typename... Args, typename = typename std::enable_if<!T::value>::type>
class A
{
// do something else
};
int main() { return 0; }
However, this program gives the following error:
prog.cpp:6:11: error: parameter pack ‘Args’ must be at the end of the
template parameter list
class A;
I have struggled to find a good source of information on the use of enable_if to select classes with variadic templates. The only question I could find is this one:
How to use std::enable_if with variadic template
But despite the name, this question and its answers aren't much help. If someone could provide or link a guide on how this should be approached and why that would be appreciated.
First of all, what you're trying is writing multiple definitions of a class template. That's not allowed because it violates One definition rule. If you want to do conditional enabling with classes, you need specializations. Also, the compiler error message already told you, you can't have a variadic parameter pack in the middle of a parameter list.
One way to do it would be:
namespace detail {
template<typename T, typename Enable, typename... Args>
class A_impl;
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<T::value>::type, Args...> {
// code here
};
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<!T::value>::type, Args...> {
// code here
};
}
template<typename T, typename...Args>
class A : public detail::A_impl<T, void, Args...> {};
Jonathan's way is also perfectly fine if the condition is really a bool, but it might not be useful if you wish to add more specializations that each depend on several conditons.
It looks as though for your purposes you don't need to enable/disable the class, you just need a partial specialization:
template <typename T, bool B = T::value, typename... Args>
class A;
template <typename T, typename... Args>
class A<T, true, Args...>;
template <typename T, typename... Args>
class A<T, false, Args...>;

Variadically templated use of std::conditional where one type is an instantiation failure

I am attempting to build a variadically templated class. As is common, each level of the instantiation needs to instantiate the "next level" by slicing off one type and then using the remainder. For my final level, rather than specialize on one type, I'd rather give some base case type and keep from duplicating the actual logic.
I've added a std::conditional to switch on the BaseCase when the rest of the types consists of an empty parameter pack.
class BaseCase { };
template <typename T, typename... Ts>
class VariadicClass;
template <typename... Ts>
using NextLevel = typename std::conditional<
sizeof...(Ts) != 0, VariadicClass<Ts...>, BaseCase>::type;
template <typename T, typename... Ts>
class VariadicClass {
T this_level; // whatever
NextLevel<Ts...> next_level; // fails when Ts is empty
};
The problem is that VariadicClass is templated on at least one type parameter, so when it hits the base case (Ts is empty), trying to use std::conditional uses VariadicClass<>, which fails of course.
The solution I've managed is to write some specific functions and use decltype along with overloads, and not use std::conditional at all.
template <typename... Ts>
VariadicClass<Ts...> type_helper(Ts&&...);
BaseCase type_helper();
template <typename... Ts>
using NextLevel = decltype(type_helper(std::declval<Ts>()...));
Now, this works, but if I want to keep up this practice every time I have a variadic class, it seems tedious. Is there a way to use std::conditional or something similar to achieve this effect without having to write out so much problem-specific code?
Defer evaluation.
template<class T>struct identity{
template<class...>using result=T;
};
template<template<class...>class src>
struct delay{
template<class...Ts>using result=src<Ts...>;
};
template <typename... Ts>
using NextLevel =
typename std::conditional<
sizeof...(Ts) != 0, delay<VariadicClass>, identity<BaseCase>
>::type::template result<Ts...>;
identity ignores the Ts... and returns its argument. delay takes a template and applies the Ts.... While the signature looks suspicious, it works.
Why not just
class BaseCase { };
template <typename... Ts>
class VariadicClass; // undefined base template
template <typename... Ts>
using NextLevel = typename std::conditional<
sizeof...(Ts) != 0, VariadicClass<Ts...>, BaseCase>::type;
template <typename T, typename... Ts>
class VariadicClass<T, Ts...> { // partial specialization for having at least 1 type parameter
T this_level; // whatever
NextLevel<Ts...> next_level;
};
After reading T.C.'s answer and Yakk's comment, I realized I could write this as one templated class with two specializations, rather than write another BaseClass and the type alias.
template <typename... Ts>
class VariadicClass;
// specialization gets everything but an empty Ts
template <typename T, typename... Ts>
class VariadicClass<T, Ts...> {
VariadicClass<Ts...> next_level;
// normal case
};
template <>
class VariadicClass<> { // instead of class BaseCase
// base case
};
Alternatively, you may specialize VariadicClass<T>
class BaseCase {};
// general case
template <typename T, typename... Ts>
class VariadicClass {
T this_level; // whatever
VariadicClass<Ts...> next_level;
};
// specialization
template <typename T>
class VariadicClass<T> {
T this_level; // whatever
BaseClass next_level;
};