懒惰评估(短路)模板条件类型的通用方法

时间:2015-02-10 13:32:28

标签: c++ templates metaprogramming c++14 template-meta-programming

在使用编译时字符串(char)操作的可变参数列表时,我需要实现一种检查编译时字符串是否包含另一个(较小的)编译时字符串的方法。

这是我的第一次尝试:

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...>;
    static constexpr int sz1{L1::size};
    static constexpr int sz2{L2::size};

    using Type = std::conditional
    <
        (I1 >= sz1),
        std::false_type,
        std::conditional
        <
            (L1::template at<I1>() != L2::template at<I2>()),
            typename Contains<I1 + 1, 0, L1, L2>::Type,
            std::conditional
            <
                (I2 == sz2 - 1),
                std::true_type,
                typename Contains<I1 + 1, I2 + 1, L1, L2>::Type
            >
        >
    >;
};

我觉得这个解决方案非常容易理解和推理。不幸的是,它不起作用

编译器总是尝试实例化std::conditional的每个分支,甚至是那些未被采用的分支。换句话说,短路没有发生。

这会导致Contains无限实例化。

我已经通过分离单独的模板类中的每个std::conditional块解决了我原来的问题,其中条件结果被处理为部分特化。

它有效,但不幸的是我觉得很难阅读/修改。


有没有办法懒惰地实例化模板类型并接近原始解决方案?

这是代码的示例:

using Type = std::conditional
<
    (I1 >= sz1),
    std::false_type,
    std::conditional
    <
        (L1::template at<I1>() != L2::template at<I2>()),
        DeferInstantiation<typename Contains<I1 + 1, 0, L1, L2>::Type>,
        std::conditional
        <
            (I2 == sz2 - 1),
            std::true_type,
            DeferInstantiation<typename Contains<I1 + 1, I2 + 1, L1, L2>::Type>
        >
    >
>;

是否有可能实施DeferInstantiation<T>

3 个答案:

答案 0 :(得分:6)

这是一个通用模板,允许通过简单地不实例化来延迟实例化:)

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...>;
};

为了完整性,一个证明其用途的简单例子:

#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;
}

打印:

OneParam
TwoParam

答案 1 :(得分:3)

  

编译器总是尝试实例化std :: conditional的每个分支,即使是那些未被采用的分支。   换句话说,短路并没有发生。

提供

std::conditional<B,T,F>是为了执行编译时 在给定的类型 TF之间进行选择,具体取决于布尔值B。该 选择是通过专业化来实现的。当B为真时,实例化的特化为:

std::conditional<true,T,F>
{
    typedef T type;
};

B为false时,实例化的特化为:

std::conditional<false,T,F>
{
    typedef F type;
};

请注意,要实例化 特化,TF必须 被实例化。 没有“分支”。 “短路”的概念 实例化std::conditional<true,T,F>std::conditional<false,T,F> 只能意味着不这样做

所以不,对于类型参数,不可能实现DeferInstantiation<U> U,以便实例化

std::conditional<{true|false},DeferInstantiation<T>,DeferInstantiation<F>>

不需要DeferInstantiation<T>DeferInstantiation<F>>的实例化, 因此TF

执行编译时选择,选择哪个或两个或更多模板 实例化后,该语言提供专业化(如图所示) 根据{{​​1}}本身的定义;它提供功能模板重载 分辨率,它提供SFINAE。 专业化和重载分辨率可以协同使用 SFINAE,通过std::enable_if<B,T>

的图书馆支持

阻碍你制作特定递归元函数的问题 您想要的不是在给定的类型之间进行选择,而是选择模板 应该引导递归实例化。std::conditional<B,T,F>不是。std::conditional 达到目的。 @Pradhan的回答表明模板与std::conditional不同 可以编写,以实现两个模板之间的编译时选择,而无需 要求他们两个都要实例化。他应用专业化来做到这一点。

正如你所说,你已经找到了专业化解决方案 问题。这原则上是递归控制的正确方法 递归元函数中的模板选择。然而,随着的到来 constexpr,递归元函数不会像市场份额那样命令 他们以前做过的问题,以及他们造成的大部分脑痛 已成为过去。

这里的特殊问题 - 在编译时确定一个字符串是否是子字符串 另一个 - 可以在不使用模板元编程的情况下解决,而无需解决 表示编译时字符串,而不是传统的字符串文字:

#include <cstddef>

constexpr std::size_t str_len(char const *s)
{
    return *s ? 1 + str_len(s + 1) : 0;
}

constexpr bool 
is_substr(char const * src, char const *targ, 
            std::size_t si = 0, std::size_t ti = 0)
{
    return  !targ[ti] ? true :
                str_len(src + si) < str_len(targ + ti) ? false :
                    src[si] == targ[ti] ? 
                        is_substr(src,targ,si + 1, ti + 1) :
                            is_substr(src,targ,si + 1, 0);
}

// Compiletime tests...

static_assert(is_substr("",""),"");
static_assert(is_substr("qwerty",""),"");
static_assert(is_substr("qwerty","qwerty"),"");
static_assert(is_substr("qwerty","qwert"),"");
static_assert(is_substr("qwerty","werty"),"");
static_assert(is_substr("qwerty","wert"),"");
static_assert(is_substr("qwerty","er"),"");
static_assert(!is_substr("qwerty","qy"),"");
static_assert(!is_substr("qwerty","et"),"");
static_assert(!is_substr("qwerty","qwertyz"),"");
static_assert(!is_substr("qwerty","pqwerty"),"");
static_assert(!is_substr("","qwerty"),"");

int main()
{
    return 0;
}

这将编译为C ++ 11或更高版本。

您可能有理由希望表示编译时字符串 作为CharList<char ...>而不是因此使它们适合 这样的TMP编译时查询。我们可以看到CharList<char ...Cs> 有一个静态常量size成员评估为sizeof...(Cs)并且有 一个静态at<N>()成员函数,评估N的{​​{1}}。 在这种情况下(假设调试...Cs),您可能会适应 at<N>()是一个期待is_substr的模板函数 参数大致如下:

CharList<char ...>

说明了SFINAE的应用,由#include <type_traits> template< class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0> constexpr typename std::enable_if<(TargI == TargList::size && SrcI <= SrcList::size),bool>::type is_substr() { return true; } template< class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0> constexpr typename std::enable_if<(TargI < TargList::size && SrcI == SrcList::size),bool>::type is_substr() { return false; } template< class SrcList, class TargList, std::size_t SrcI = 0, std::size_t TargI = 0> constexpr typename std::enable_if<(TargI < TargList::size && SrcI < SrcList::size),bool>::type is_substr() { return SrcList::template at<SrcI>() == TargList::template at<TargI>() ? is_substr<SrcList,TargList,SrcI + 1,TargI + 1>() : is_substr<SrcList,TargList,SrcI + 1,0>(); }

利用

最后,您也可能对此计划感兴趣:

std::enable_if

打印:

#include <iostream>

template<char const * Arr>
struct string_lit_type 
{
    static constexpr const char * str = Arr;
    static constexpr std::size_t size = str_len(str);
    static constexpr char at(std::size_t i) {
        return str[i];
    }
};

constexpr char arr[] = "Hello World\n";

int main()
{
    std::cout << string_lit_type<arr>::str;
    std::cout << string_lit_type<arr>::size << std::endl;
    std::cout << string_lit_type<arr>::at(0) << std::endl;
    return 0;
}

(使用g ++ 4.9,clang 3.5编译的代码)

答案 2 :(得分:1)

我同意OP,不幸的是std :: conditional中没有短路(或者在未输入的分支中将其称为SFINAE,这样不正确的类型不会导致错误)。

我的代码中有同样的问题,可以通过在constexpr lambda中使用if constexpr来解决。因此,代替

using type = std::conditional_t<logical, A, B>;

使用

auto get_type = []()
{
    if constexpr(logical)
    {
        return std::declval<A>();
    }
    else
    {
        return std::declval<B>();
    }
};
using type = decltype(get_type());

但是,到目前为止,它的可读性较差。