在玩编译时的字符串(变量列表的 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>
?
这里有一个通用模板,允许通过简单的不实例化来进行延迟实例化 :)
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
编译器总是试图实例化 std::conditional 的每一个分支,即使是那些没有被采纳的分支。 换一种说法,就是没有发生短路。
std::conditional<B,T,F>
的目的是为了在给定的 类型 T
和 F
,取决于布尔值 B
. 选择是受专业化影响的。当 B
是真的,实例化的特殊化是。
std::conditional<true,T,F>
{
typedef T type;
};
而当 B
是假的,实例化的特殊化是。
std::conditional<false,T,F>
{
typedef F type;
};
请注意,要实例化 要么 专门化,既 T
和 F
必须被实例化。没有 "分支". "短路 "的概念是指在任何一种情况下,都可以通过 "短路 "来实现。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编译)
我同意上位者的观点,在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());
然而,它的可读性却大打折扣。