如何在 C++11 中为 SFINAE 和静态断言定义约束
how to define constraints for SFINAE and static assert in C++11
我正在 serialization/deserialization 使用模板进行试验,现在可以正常使用了。
显然,在实现它时,我 运行 遇到了很多麻烦,有数百个编译器错误日志。
在进一步扩展我的库之前,我想使用 SFINAE(到目前为止我很少使用它)和 static_asserts 来稍微保护一下它。
不要再弄乱我的库了,我正在沙盒中训练:
http://coliru.stacked-crooked.com/a/9eb4eaefaac90fc0
我想定义几个谓词:
- is_a_base 检查对象派生自 Base。
- is_insertable & is_extractable 检查是否为该类型定义了 operator>> 和 operator<<。
我希望能够将这些谓词用于 SFINAE 专业化和 static_assert。
#include <sstream>
#include <iostream>
//forward declaration
class Base;
//"Template typedef" to check predicates
template <typename T>
using is_a_Base = typename std::enable_if<std::is_base_of<Base, T>::value, void>::type;
template <typename T, typename is = std::istream>
using is_extractable = decltype (is{} >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (os{} << T{});
//Test classes
class Base{
public:
std::string getStr(){ return "Base.getStr()";}
};
class Derived: public Base {};
class Other{};
//A template class with its specializations with SFINAE
template <typename T, typename Enable = void>
class C{
public:
static void f(T& o){
std::cout << "f<T> default !" << std::endl;
}
};
template<typename T>
class C<T, is_a_Base<T>>
{
public:
static void f (T& o)
{
std::cout << "f<is_a_A>() ! " << o.getStr() << std::endl;
}
};
template<typename T>
class C<T, is_insertable<T> >
{
public:
static void f (T& o)
{
std::cout << "f<is_insertable() ! " << o << std::endl;
}
};
template<typename T>
std::string g(T& ref)
{
//static_assert(is_a_Base<T>, "T is not a Base"); //can't figure out the syntax here
return ref.getStr();
}
int main(){
Base a;
Derived b;
int myint = 1;
std::string str="toto";
Other oops;
C<Base>::f(a);
C<Derived>::f(b);
C<int>::f(myint); //Not calling is_insertable ??
C<std::string>::f(str); //Not calling is_insertable ??
C<Other>::f(oops);
std::cout << "g:" << g(a) << std::endl;
//std::cout << "g:" << g(oops) << std::endl; //should be blasted by the static assert
}
结果:
f<is_a_A>() ! Base.getStr()
f<is_a_A>() ! Base.getStr()
f<T> default !
f<T> default !
f<T> default !
g:Base.getStr()
到目前为止,is_a_base 正在为 SFINAE 工作。
但是,is_insertable 不适用于 int 和 string 变量?
我也不知道如何正确地将 is_a_base 谓词重用到我的断言语句中。
(由于我受限于不支持超过 C++11 的交叉编译器,我无法利用 C++14 及更高版本的优势。)
两个问题:
std::ostream
和 std::istream
没有默认构造函数,因此按照您的方式创建它们会导致替换失败,因此主模板被用作后备。使用 declval<T&>
以避免使用默认构造函数创建临时对象。
主模板中的第二个模板参数必须是 void
才能被选中。正如你所拥有的,它 returns [i/o]s{} << T{}
(sic) 的结果类型将是 std::ostream&
,另一个是 std::istream&
。您可以通过将第二个参数包装在 std::void_t
.
中来解决此问题
#1
template <typename T, typename is = std::istream>
using is_extractable = decltype (std::declval<is&>() >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (std::declval<os&>() << T{});
#2
template<typename T>
class C<T, std::void_t<is_a_Base<T>>>
template<typename T>
class C<T, std::void_t<is_insertable<T>>>
您正在使用 SFINAE 通过默认参数 void
select C
的特化。这意味着专业化的签名必须正确替换,但 也 为要 selected 的专业化生成 void
。就目前而言,您的流特征:
template <typename T, typename is = std::istream>
using is_extractable = decltype (is{} >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (os{} << T{});
... 实际上从 operator >>
(resp. <<
)生成 return 类型,通常是 is &
(resp. os &
)。您的命名也有点令人困惑,因为 is_...
暗示一个布尔值,但它们不是。因此我建议的修复:
模板
using enable_if_base = typename std::enable_if<std::is_base_of<Base, T>::value>::type;
// `void` is the default already ^
template <typename T, typename is = std::istream>
using enable_if_extractable = decltype (std::declval<is &>() >> std::declval<T &>(), void());
// ^^^^^^^^
template <typename T, typename os = std::ostream>
using enable_if_insertable = decltype (std::declval<os &>() << std::declval<T const &>(), void());
// ^^^^^^^^
请注意,我还用适当限定的 std::declval
替换了 T{}
,因为您不能假设相关类型实际上是可默认构造的——事实上,std::istream
和 std::ostream
不是。
我正在 serialization/deserialization 使用模板进行试验,现在可以正常使用了。 显然,在实现它时,我 运行 遇到了很多麻烦,有数百个编译器错误日志。 在进一步扩展我的库之前,我想使用 SFINAE(到目前为止我很少使用它)和 static_asserts 来稍微保护一下它。
不要再弄乱我的库了,我正在沙盒中训练:
http://coliru.stacked-crooked.com/a/9eb4eaefaac90fc0
我想定义几个谓词:
- is_a_base 检查对象派生自 Base。
- is_insertable & is_extractable 检查是否为该类型定义了 operator>> 和 operator<<。
我希望能够将这些谓词用于 SFINAE 专业化和 static_assert。
#include <sstream>
#include <iostream>
//forward declaration
class Base;
//"Template typedef" to check predicates
template <typename T>
using is_a_Base = typename std::enable_if<std::is_base_of<Base, T>::value, void>::type;
template <typename T, typename is = std::istream>
using is_extractable = decltype (is{} >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (os{} << T{});
//Test classes
class Base{
public:
std::string getStr(){ return "Base.getStr()";}
};
class Derived: public Base {};
class Other{};
//A template class with its specializations with SFINAE
template <typename T, typename Enable = void>
class C{
public:
static void f(T& o){
std::cout << "f<T> default !" << std::endl;
}
};
template<typename T>
class C<T, is_a_Base<T>>
{
public:
static void f (T& o)
{
std::cout << "f<is_a_A>() ! " << o.getStr() << std::endl;
}
};
template<typename T>
class C<T, is_insertable<T> >
{
public:
static void f (T& o)
{
std::cout << "f<is_insertable() ! " << o << std::endl;
}
};
template<typename T>
std::string g(T& ref)
{
//static_assert(is_a_Base<T>, "T is not a Base"); //can't figure out the syntax here
return ref.getStr();
}
int main(){
Base a;
Derived b;
int myint = 1;
std::string str="toto";
Other oops;
C<Base>::f(a);
C<Derived>::f(b);
C<int>::f(myint); //Not calling is_insertable ??
C<std::string>::f(str); //Not calling is_insertable ??
C<Other>::f(oops);
std::cout << "g:" << g(a) << std::endl;
//std::cout << "g:" << g(oops) << std::endl; //should be blasted by the static assert
}
结果:
f<is_a_A>() ! Base.getStr()
f<is_a_A>() ! Base.getStr()
f<T> default !
f<T> default !
f<T> default !
g:Base.getStr()
到目前为止,is_a_base 正在为 SFINAE 工作。 但是,is_insertable 不适用于 int 和 string 变量? 我也不知道如何正确地将 is_a_base 谓词重用到我的断言语句中。
(由于我受限于不支持超过 C++11 的交叉编译器,我无法利用 C++14 及更高版本的优势。)
两个问题:
std::ostream
和std::istream
没有默认构造函数,因此按照您的方式创建它们会导致替换失败,因此主模板被用作后备。使用declval<T&>
以避免使用默认构造函数创建临时对象。主模板中的第二个模板参数必须是
void
才能被选中。正如你所拥有的,它 returns[i/o]s{} << T{}
(sic) 的结果类型将是std::ostream&
,另一个是std::istream&
。您可以通过将第二个参数包装在std::void_t
. 中来解决此问题
#1
template <typename T, typename is = std::istream>
using is_extractable = decltype (std::declval<is&>() >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (std::declval<os&>() << T{});
#2
template<typename T>
class C<T, std::void_t<is_a_Base<T>>>
template<typename T>
class C<T, std::void_t<is_insertable<T>>>
您正在使用 SFINAE 通过默认参数 void
select C
的特化。这意味着专业化的签名必须正确替换,但 也 为要 selected 的专业化生成 void
。就目前而言,您的流特征:
template <typename T, typename is = std::istream>
using is_extractable = decltype (is{} >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (os{} << T{});
... 实际上从 operator >>
(resp. <<
)生成 return 类型,通常是 is &
(resp. os &
)。您的命名也有点令人困惑,因为 is_...
暗示一个布尔值,但它们不是。因此我建议的修复:
模板
using enable_if_base = typename std::enable_if<std::is_base_of<Base, T>::value>::type;
// `void` is the default already ^
template <typename T, typename is = std::istream>
using enable_if_extractable = decltype (std::declval<is &>() >> std::declval<T &>(), void());
// ^^^^^^^^
template <typename T, typename os = std::ostream>
using enable_if_insertable = decltype (std::declval<os &>() << std::declval<T const &>(), void());
// ^^^^^^^^
请注意,我还用适当限定的 std::declval
替换了 T{}
,因为您不能假设相关类型实际上是可默认构造的——事实上,std::istream
和 std::ostream
不是。