延迟评估(短路)模板条件类型的通用方法
Generic way of lazily evaluating (short-circuiting) template conditional types
在处理编译时字符串(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
块分隔在一个单独的模板 class 中解决了我原来的问题,其中条件结果作为部分特化处理。
它有效,但不幸的是我发现它很难read/modify。
有没有办法延迟实例化模板类型并接近我原来的解决方案?
这是代码的示例:
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
The compiler always tries to instantiate every single branch of std::conditional, even those which are not taken.
To put it in another way, short-circuiting isn't happening.
std::conditional<B,T,F>
是为了执行编译时而提供的
根据布尔值 B
,在给定的 types T
和 F
之间进行选择。这
选择受专业化影响。当 B
为真时,实例化特化为:
std::conditional<true,T,F>
{
typedef T type;
};
当 B
为 false 时,实例化特化为:
std::conditional<false,T,F>
{
typedef F type;
};
请注意,要实例化 任一 特化,T
和 F
都必须
被实例化。 没有"branches"。 "short-circuiting" 的概念
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
.
用于执行关于哪个或两个或更多模板的编译时选择
实例化后,该语言提供 specialization(如刚才所示
根据 std::conditional<B,T,F>
本身的定义);它提供 函数模板重载
分辨率,提供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;
}
这将编译为 C++11 或更好的版本。
您可能有理由希望表示编译时字符串
CharList<char ...>
除了让他们服从
诸如此类的 TMP 编译时查询。我们可以看到 CharList<char ...Cs>
有一个静态常量 size
成员评估为 sizeof...(Cs)
并且有
一个静态 at<N>()
成员函数计算到 ...Cs
的第 N
个。
在那种情况下(假设 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编译的代码)
我同意 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());
然而,它的可读性要差得多。
在处理编译时字符串(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
块分隔在一个单独的模板 class 中解决了我原来的问题,其中条件结果作为部分特化处理。
它有效,但不幸的是我发现它很难read/modify。
有没有办法延迟实例化模板类型并接近我原来的解决方案?
这是代码的示例:
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
The compiler always tries to instantiate every single branch of std::conditional, even those which are not taken. To put it in another way, short-circuiting isn't happening.
std::conditional<B,T,F>
是为了执行编译时而提供的
根据布尔值 B
,在给定的 types T
和 F
之间进行选择。这
选择受专业化影响。当 B
为真时,实例化特化为:
std::conditional<true,T,F>
{
typedef T type;
};
当 B
为 false 时,实例化特化为:
std::conditional<false,T,F>
{
typedef F type;
};
请注意,要实例化 任一 特化,T
和 F
都必须
被实例化。 没有"branches"。 "short-circuiting" 的概念
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
.
用于执行关于哪个或两个或更多模板的编译时选择
实例化后,该语言提供 specialization(如刚才所示
根据 std::conditional<B,T,F>
本身的定义);它提供 函数模板重载
分辨率,提供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;
}
这将编译为 C++11 或更好的版本。
您可能有理由希望表示编译时字符串
CharList<char ...>
除了让他们服从
诸如此类的 TMP 编译时查询。我们可以看到 CharList<char ...Cs>
有一个静态常量 size
成员评估为 sizeof...(Cs)
并且有
一个静态 at<N>()
成员函数计算到 ...Cs
的第 N
个。
在那种情况下(假设 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编译的代码)
我同意 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());
然而,它的可读性要差得多。