Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 710
Ryan Haining Avatar asked Dec 15 '22 20:12

Ryan Haining


2 Answers

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.

like image 89
Yakk - Adam Nevraumont Avatar answered Jan 17 '23 15:01

Yakk - Adam Nevraumont


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;
};
like image 41
T.C. Avatar answered Jan 17 '23 15:01

T.C.