首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >惰性评估(短路)模板条件类型的一般方法

惰性评估(短路)模板条件类型的一般方法
EN

Stack Overflow用户
提问于 2015-02-10 13:32:27
回答 3查看 630关注 0票数 16

在处理编译时字符串(char的各种列表)时,我需要实现一种检查编译时字符串是否包含另一个(较小的)编译时字符串的方法。

这是我第一次尝试:

代码语言:javascript
复制
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的每个分支,即使是那些未被接受的分支。换句话说,short-circuiting是不会发生的。

这将导致Contains被无限地实例化。

我解决了最初的问题,将每个std::conditional块分离到一个单独的模板类中,其中的条件结果作为部分专门化处理。

它可以工作,但不幸的是,我发现它很难阅读/修改。

是否有一种方法可以惰性地实例化模板类型并接近我的原始解决方案?

这是代码可能是什么样子的示例:

代码语言:javascript
复制
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>

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-02-10 14:52:43

下面是一个允许延迟实例化的通用模板,只需不实例化:)

代码语言:javascript
复制
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...>;
};

为了完整起见,一个简单的例子演示了它的使用:

代码语言:javascript
复制
#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;
}

这些指纹:

代码语言:javascript
复制
OneParam
TwoParam
票数 6
EN

Stack Overflow用户

发布于 2015-02-13 20:22:51

编译器总是尝试实例化std::条件的每个分支,甚至那些未被接受的分支。换句话说,短路是不可能发生的。

提供std::conditional<B,T,F>是为了执行给定类型TF之间的编译时选择,具体取决于布尔值B。这种选择受到专业化的影响。当B为真时,实例化的专门化是:

代码语言:javascript
复制
std::conditional<true,T,F>
{
    typedef T type;
};

B为false时,实例化的专门化是:

代码语言:javascript
复制
std::conditional<false,T,F>
{
    typedef F type;
};

请注意,要实例化两个专门化,必须同时实例化TF。没有“分支”。“短路”的概念-- std::conditional<true,T,F>std::conditional<false,T,F>的实例化--只能意味着不去做。

因此,不可能为类型参数DeferInstantiation<U>实现U,例如

代码语言:javascript
复制
std::conditional<{true|false},DeferInstantiation<T>,DeferInstantiation<F>>

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

为了执行编译时选择实例化哪些模板或两个或多个模板,该语言提供专门化(正如std::conditional<B,T,F>本身的定义所示);它提供函数模板重载解析,并提供SFINAE。通过std::enable_if的库支持,专业化和过载解决可以与SFINAE协同使用。

阻碍您构建特定递归元函数的问题不是在给定类型之间进行选择,而是选择递归实例化应该指向的模板。std::conditional不是为了达到这个目的。@Pradhan的回答表明,可以编写与std::conditional不同的模板,以便在两个模板之间进行编译时选择,而不需要实例化这两个模板。他专门从事这项工作。

正如您所说的,您已经为这个问题找到了专门化的解决方案。原则上,这是递归控制递归元函数中模板选择的正确方法。然而,随着constexpr的出现,递归元函数不再像以前那样占据市场份额,它们所引起的大部分脑痛都已成为过去。

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

代码语言:javascript
复制
#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>()成员函数计算到...CsNth。在这种情况下(假设对at<N>()进行了调试),您可能会将is_substr调整为一个模板函数,希望在大致如下的行中使用CharList<char ...>参数:

代码语言:javascript
复制
#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技术实现高精度声发射的应用。

最后,您还可以对此项目感兴趣:

代码语言:javascript
复制
#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;
}

其中的指纹:

代码语言:javascript
复制
Hello World
12
H

(用g++ 4.9编译的代码,clang3.5)

票数 7
EN

Stack Overflow用户

发布于 2020-04-23 07:52:06

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

我的代码中也有同样的问题,我可以通过使用if constexpr来解决这个问题。所以,而不是

代码语言:javascript
复制
using type = std::conditional_t<logical, A, B>;

使用

代码语言:javascript
复制
auto get_type = []()
{
    if constexpr(logical)
    {
        return std::declval<A>();
    }
    else
    {
        return std::declval<B>();
    }
};
using type = decltype(get_type());

然而,它的可读性要低得多。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/28432977

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档