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

Vit*_*meo 14 c++ templates metaprogramming template-meta-programming c++14

在使用编译时字符串(可变参数列表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
            >
        >
    >;
};
Run Code Online (Sandbox Code Playgroud)

我发现这个解决方案非常容易阅读和推理.不幸的是,它不起作用.

编译器总是尝试实例化每个分支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>
        >
    >
>;
Run Code Online (Sandbox Code Playgroud)

是否有可能实施DeferInstantiation<T>

Pra*_*han 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...>;
};
Run Code Online (Sandbox Code Playgroud)

为了完整性,一个简单的例子展示了它的用途:

#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;
}
Run Code Online (Sandbox Code Playgroud)

这打印:

OneParam
TwoParam
Run Code Online (Sandbox Code Playgroud)


Mik*_*han 5

编译器总是尝试实例化std :: conditional的每个单个分支,即使那些未使用的分支也是如此。换句话说,不会发生短路。

std::conditional<B,T,F>提供此属性是为了在给定类型 TF取决于boolean的类型之间执行编译时选择B。选择受专业化的影响。当B为true时,实例化的专业化为:

std::conditional<true,T,F>
{
    typedef T type;
};
Run Code Online (Sandbox Code Playgroud)

B为false时,实例化的专业化为:

std::conditional<false,T,F>
{
    typedef F type;
};
Run Code Online (Sandbox Code Playgroud)

请注意,以实例要么专业化,无论是TF必须被实例化。没有“分支”。“短路”任一std::conditional<true,T,F>或实例的概念std::conditional<false,T,F> 仅意味着不这样做

因此,不能DeferInstantiation<U>,对于type parameter U,无法实现的实例化

std::conditional<{true|false},DeferInstantiation<T>,DeferInstantiation<F>>
Run Code Online (Sandbox Code Playgroud)

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

为了执行关于实例化哪个或两个或更多模板的编译时选择,该语言提供了专门化(如其std::conditional<B,T,F>自身的定义所示);它提供功能模板重载解析,并提供SFINAE。通过SFINAE,可以通过SFINAE协同利用专业化和重载解决方案std::enable_if<B,T>

阻碍您设计所需的特定递归元功能的问题不是在给定类型之间进行选择,而是选择将 递归实例化应针对的模板之一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;
}
Run Code Online (Sandbox Code Playgroud)

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

您可能有其他理由希望将编译时字符串表示为,CharList<char ...>而不是使它们适合诸如TMP这样的编译时查询。我们可以看到,CharList<char ...Cs> 有一个静态常数size成员求和sizeof...(Cs),有一个静态at<N>()成员函数求和的Nth ...Cs。在这种情况下(假设at<N>()已调试),您可能会适应 is_substr成为一个模板函数,并期望CharList<char ...> 大致在以下几行中获得参数:

#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>();
}
Run Code Online (Sandbox Code Playgroud)

该图说明了SFINAE的应用, 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;
}
Run Code Online (Sandbox Code Playgroud)

打印:

Hello World
12
H
Run Code Online (Sandbox Code Playgroud)

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