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

问题描述 投票:15回答:3

在玩编译时的字符串(变量列表的 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>?

c++ templates metaprogramming c++14 template-meta-programming
3个回答
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

5
投票

编译器总是试图实例化 std::conditional 的每一个分支,即使是那些没有被采纳的分支。 换一种说法,就是没有发生短路。

std::conditional<B,T,F> 的目的是为了在给定的 类型 TF,取决于布尔值 B. 选择是受专业化影响的。当 B 是真的,实例化的特殊化是。

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

而当 B 是假的,实例化的特殊化是。

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>>因此 T以及 F.

为了执行编译时选择哪一个或两个或更多的 模板 应予证实,该语言规定 专业化 (正如刚才的定义所说明的那样) std::conditional<B,T,F> 本身);它提供了 功能模板过载分辨率它提供 SFINAE.专业化和过载解决都可以通过SFINAE的库支持协同利用。std::enable_if<B,T>

阻碍你制作你想要的特定递归元函数的问题,并不是在给定的 类型 但选择的 模板递归实例应指向其中。std::conditional 是不符合目的的。@Pradhan的回答表明,一个不同于以往的模板的 std::conditional可以很好地写成在编译时在两个 模板,而不需要将它们都实例化。他应用特殊化来做。

如你所说,你已经想出了一个特殊化的解决方法来解决这个问题。原则上这是递归元函数中递归控制模板选择的正确方法。然而,随着 constexpr现在,递归元函数在问题中的市场占有率已经不像以前那样高了,它们所带来的大部分脑力伤害也已经成为过去。

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

#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之三 ...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>();
}

这说明了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;
}

这个程序可以打印:

Hello World
12
H

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


1
投票

我同意上位者的观点,在std::conditional中没有短路是很不幸的(或者在未输入的分支中叫它SFINAE,这样就不会导致错误的类型)。

我的代码中也有同样的问题,可以通过使用 if constexpr 在constexpr lambda中。所以,与其说是

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());

然而,它的可读性却大打折扣。

© www.soinside.com 2019 - 2024. All rights reserved.