While playing around with compile-time string (variadic lists of char
) manipulation, I needed to implement a way of checking if a compile-time string contained another (smaller) compile-time string.
This was my first attempt:
template<int I1, int I2, typename, typename> struct Contains;
template<int I1, int I2, char... Cs1, char... Cs2>
struct Contains<I1, I2, CharList<Cs1...>, CharList<Cs2...>>
{
using L1 = CharList<Cs1...>;
using L2 = CharList<Cs2...>;
using Type = std::conditional_t
<
(I1 >= sizeof...(Cs1)),
std::false_type,
std::conditional
<
(L1::template at<I1>() != L2::template at<I2>()),
typename Contains<I1 + 1, 0, L1, L2>::Type,
std::conditional
<
(I2 == sizeof...(Cs2) - 1),
std::true_type,
typename Contains<I1 + 1, I2 + 1, L1, L2>::Type
>
>
>;
};
I find this solution extremely easy to read and reason about. Unfortunately, it doesn't work.
The compiler always tries to instantiate every single branch of std::conditional
, even those which are not taken. To put it in another way, short-circuiting isn't happening.
This causes Contains
to be instantiated infinitely.
I've solved my original problem by separating every std::conditional
block in a separate template class where the condition results are handled as partial specializations.
It works, but unfortunately I find it very hard to read/modify.
Is there a way to lazily instantiate a template type and be close to my original solution?
I was thinking that I could wrap the greedily instantiated templates in a type like:
DeferInstantiation<typename Contains<I1 + 1, 0, L1, L2>::Type>
This should prevent greedy instantiations of the template argument.
Is it somehow possible to implement DeferInstantiation<T>
?
Here's a generic template to allow deferred instantiation by simply not instantiating :)
template <bool B, template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ArgsTuple>
struct LazyConditional;
template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args>
struct LazyConditional<true, TrueTemplate, FalseTemplate, std::tuple<Args...>>
{
using type = TrueTemplate<Args...>;
};
template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args>
struct LazyConditional<false, TrueTemplate, FalseTemplate, std::tuple<Args...>>
{
using type = FalseTemplate<Args...>;
};
For completeness, a simple example demonstrating its use:
#include <iostream>
#include <type_traits>
#include <tuple>
template <typename T>
struct OneParam
{
void foo(){std::cout << "OneParam" << std::endl;}
};
template <typename T, typename U>
struct TwoParam
{
void foo(){std::cout << "TwoParam" << std::endl;}
};
template <bool B, template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ArgsTuple>
struct LazyConditional;
template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args>
struct LazyConditional<true, TrueTemplate, FalseTemplate, std::tuple<Args...>>
{
using type = TrueTemplate<Args...>;
};
template <template <typename...> class TrueTemplate, template <typename...> class FalseTemplate, typename ... Args>
struct LazyConditional<false, TrueTemplate, FalseTemplate, std::tuple<Args...>>
{
using type = FalseTemplate<Args...>;
};
template <typename ... Args>
struct OneOrTwoParam
{
using type = typename LazyConditional<sizeof...(Args)==1, OneParam, TwoParam, std::tuple<Args...> >::type;
};
int main()
{
OneOrTwoParam<int>::type().foo();
OneOrTwoParam<int, int>::type().foo();
return 0;
}
This prints:
OneParam
TwoParam