SFINAE 断言()代码不编译
SFINAE to assert() that code DOES NOT compile
我确信一定可以使用 SFINAE(可能与宏一起)来 static_assert() 任意代码 不会 编译。
我的代码库中有一些复杂的情况,我有一个 class 我想禁止使用临时对象(我相信模式是):
class(const class&& tmp)=delete; /* deny copying from an unnamed temporary */
class (class&& rhs){/* allow construction from non-temporary rvalue*/}
目前,我检查了一个不需要的构造函数不编译,但当然我必须将其注释掉以使测试再次编译!
如果我能做到:
static_assert(DOES_NOT_COMPILE(myclass_t("Hello")));
const char* help = "HELP";
static_assert(!DOES_NOT_COMPILE(myclass_t(help)));
// COMPILES() might be better here :-)
这对我有很大帮助,但我似乎找不到通用的 SFINAE 解决方案。仅限 C++14,因此:
if constexpr
不可用。
以下宏可让您以 SFINAE-friendly 方式重写 SFINAE-unfriendly 表达式,例如 [](auto&&x) { return x+1; }
。
#define RETURNS(...)\
noexcept(noexcept(__VA_ARGS__))\
->decltype(__VA_ARGS__)\
{ return __VA_ARGS__;}
这样您就可以像这样重写上面的 lambda 表达式:
[](auto&&x) RETURNS( x+1 )
或者,另一个例子:
struct { template<class X> auto operator()(X&&x) RETURNS(x+1) };
而且它对 SFINAE 很友好。 RETURNS
实际上不是必需的,但它使大部分代码更加清晰。 SO 自己的@barry 提出了 c++20 将 RETURNS
替换为 =>
的提议。
接下来我们需要能够测试是否可以调用函数对象。
namespace details {
template<class, class, class...>
struct can_invoke:std::false_type{};
template<class F, class...Args>
struct can_invoke<
F,
std::void_t<std::result_of_t<F&&(Args&&...)>>,
Args...
>:
std::true_type
{};
}
template<class F, class...Args>
using can_invoke=details::can_invoke<F,void,Args...>;
我们快到了。 (这是该技术的核心;我有时在这里使用 can_apply
而不是 class F
。) c++17 具有相似的特征;它可以代替使用。
test_invoke
需要可调用和 returns 可调用测试器。可调用测试器接受参数,并且 returns true 或 false 类型基于 "could the original callable be called with these arguments".
template<class F>
constexpr auto test_invoke(F&&){
return [](auto&&...args) RETURNS( can_invoke< F, decltype(args)... >{} );
}
我们到了。 test_invoke
如果您愿意使用纯类型,可以跳过,但使用值可以消除一些错误。
auto myclass_ctor=[](auto&&...args)RETURNS(myclass_t(decltype(args)(args)...));
myclass_ctor
是一个可调用对象,表示构造myclass_t
。
static_assert(!test_invoke(myclass_ctor)("Hello") );
或
template<class C>
auto ctor=[](auto&&...args)RETURNS(C(decltype(args)(args)...));
static_assert(!test_invoke(ctor<myclass_t>)("Hello") );
这需要 constexpr lambda,这是一个 c++17 特性,但还是一个早期的特性。没有它也可以完成,但它会变得丑陋。加上省略的 move ctor 要求在 c++14.
中变通很烦人
转换为 c++14,将每个 lambda 替换为具有适当的 constexpr 特殊成员函数的手动函数对象。 RETURNS
同样适用于 operator()
,如上所示。
为了绕过省略移动构造函数要求,RETURNS(void( blah ))
。
对于任何错误,我们深表歉意;我在 phone.
以@Yakk 的回答为基础,我觉得这很了不起。我们永远不能希望
static_assert(!DOES_NOT_COMPILE(myclass_t(help)));
因为必须要有类型依赖才能延迟报错,而Yakk就是这么做的。使用另一个宏,连同默认的 lambda 捕获:
STATIC_ASSERT_NOT_COMPILES(myclass_t(MK_DEP(help)));
MAKE_DEP
是一个模板化的函数对象,由宏注入,提供所需的依赖。使用示例:
void foo(){
std::string s;
const std::string cs;
STATIC_ASSERT_NOT_COMPILES(cs=MK_DEP(s));
STATIC_ASSERT_NOT_COMPILES(MK_DEP(cs).clear());
// This fires, because s can be cleared:
//STATIC_ASSERT_NOT_COMPILES(MK_DEP(s).clear()); // Fails to compile, OK!
class C{}; // just an example class
C c;
STATIC_ASSERT_NOT_COMPILES(c=MK_DEP(7));
STATIC_ASSERT_NOT_COMPILES(7=MK_DEP(c));
STATIC_ASSERT_NOT_COMPILES(baz(foo(MK_DEP(7)=c)));
STATIC_ASSERT_NOT_COMPILES(MK_DEP(false)=1);
// What about constructing C from string?
STATIC_ASSERT_NOT_COMPILES(C(MK_DEP(std::string{})));
// assert fires: can add strings: OK!
//STATIC_ASSERT_NOT_COMPILES(MK_DEP(s)+cs+std::string());
// Too many arguments to MK_DEP is forced to give hard error: Fails to compile, OK!
// STATIC_ASSERT_NOT_COMPILES(MK_DEP(1,2,3)+1);
// Forgetting to add MK_DEP also gives a *hard* error. Fails to compile. OK!
// STATIC_ASSERT_NOT_COMPILES(7=c);
}
实现,依赖Yakk的test_invoke
和RETURNS
。欢迎反馈!
namespace details{
struct make_depend{
template<typename T> static constexpr const bool false_t = false;
template<typename T>
auto operator()(T&& arg) RETURNS(arg) ;
// Try to protect against wrong use: zero or many arguments:
template<typename T, typename... T2>
auto operator()(T&& arg, T2... too_many_arguments) {
static_assert(false_t<T>, "Too many arguments given to MK_DEP"); } ;
template<typename T=int>
auto operator()() { static_assert(false_t<T>, "Too few arguments given to MK_DEP"); } ;
};
}
#define STATIC_ASSERT_NOT_COMPILES(...)\
static_assert(!test_invoke([&](auto MK_DEP)RETURNS(__VA_ARGS__))\
(details::make_depend{}))
或者,一种不那么包装的方法:
#define CHECK_COMPILES(...)\
test_invoke([&](auto MK_DEP)RETURNS(__VA_ARGS__))(details::make_depend{})
static_assert(CHECK_COMPILES(cs=MK_DEP(s)));
甚至只是基本的想法:
static_assert(test_invoke([&](auto MK_DEP)RETURNS(s+MK_DEP(s)))(details::make_depend{}));
编辑: 可变参数 operator()
只是为了防止某些情况下错误使用 MK_DEP
。出于同样的原因,我还添加了一个 no-argument 版本。
我确信一定可以使用 SFINAE(可能与宏一起)来 static_assert() 任意代码 不会 编译。
我的代码库中有一些复杂的情况,我有一个 class 我想禁止使用临时对象(我相信模式是):
class(const class&& tmp)=delete; /* deny copying from an unnamed temporary */
class (class&& rhs){/* allow construction from non-temporary rvalue*/}
目前,我检查了一个不需要的构造函数不编译,但当然我必须将其注释掉以使测试再次编译!
如果我能做到:
static_assert(DOES_NOT_COMPILE(myclass_t("Hello")));
const char* help = "HELP";
static_assert(!DOES_NOT_COMPILE(myclass_t(help)));
// COMPILES() might be better here :-)
这对我有很大帮助,但我似乎找不到通用的 SFINAE 解决方案。仅限 C++14,因此:
if constexpr
不可用。
以下宏可让您以 SFINAE-friendly 方式重写 SFINAE-unfriendly 表达式,例如 [](auto&&x) { return x+1; }
。
#define RETURNS(...)\
noexcept(noexcept(__VA_ARGS__))\
->decltype(__VA_ARGS__)\
{ return __VA_ARGS__;}
这样您就可以像这样重写上面的 lambda 表达式:
[](auto&&x) RETURNS( x+1 )
或者,另一个例子:
struct { template<class X> auto operator()(X&&x) RETURNS(x+1) };
而且它对 SFINAE 很友好。 RETURNS
实际上不是必需的,但它使大部分代码更加清晰。 SO 自己的@barry 提出了 c++20 将 RETURNS
替换为 =>
的提议。
接下来我们需要能够测试是否可以调用函数对象。
namespace details {
template<class, class, class...>
struct can_invoke:std::false_type{};
template<class F, class...Args>
struct can_invoke<
F,
std::void_t<std::result_of_t<F&&(Args&&...)>>,
Args...
>:
std::true_type
{};
}
template<class F, class...Args>
using can_invoke=details::can_invoke<F,void,Args...>;
我们快到了。 (这是该技术的核心;我有时在这里使用 can_apply
而不是 class F
。) c++17 具有相似的特征;它可以代替使用。
test_invoke
需要可调用和 returns 可调用测试器。可调用测试器接受参数,并且 returns true 或 false 类型基于 "could the original callable be called with these arguments".
template<class F>
constexpr auto test_invoke(F&&){
return [](auto&&...args) RETURNS( can_invoke< F, decltype(args)... >{} );
}
我们到了。 test_invoke
如果您愿意使用纯类型,可以跳过,但使用值可以消除一些错误。
auto myclass_ctor=[](auto&&...args)RETURNS(myclass_t(decltype(args)(args)...));
myclass_ctor
是一个可调用对象,表示构造myclass_t
。
static_assert(!test_invoke(myclass_ctor)("Hello") );
或
template<class C>
auto ctor=[](auto&&...args)RETURNS(C(decltype(args)(args)...));
static_assert(!test_invoke(ctor<myclass_t>)("Hello") );
这需要 constexpr lambda,这是一个 c++17 特性,但还是一个早期的特性。没有它也可以完成,但它会变得丑陋。加上省略的 move ctor 要求在 c++14.
中变通很烦人转换为 c++14,将每个 lambda 替换为具有适当的 constexpr 特殊成员函数的手动函数对象。 RETURNS
同样适用于 operator()
,如上所示。
为了绕过省略移动构造函数要求,RETURNS(void( blah ))
。
对于任何错误,我们深表歉意;我在 phone.
以@Yakk 的回答为基础,我觉得这很了不起。我们永远不能希望
static_assert(!DOES_NOT_COMPILE(myclass_t(help)));
因为必须要有类型依赖才能延迟报错,而Yakk就是这么做的。使用另一个宏,连同默认的 lambda 捕获:
STATIC_ASSERT_NOT_COMPILES(myclass_t(MK_DEP(help)));
MAKE_DEP
是一个模板化的函数对象,由宏注入,提供所需的依赖。使用示例:
void foo(){
std::string s;
const std::string cs;
STATIC_ASSERT_NOT_COMPILES(cs=MK_DEP(s));
STATIC_ASSERT_NOT_COMPILES(MK_DEP(cs).clear());
// This fires, because s can be cleared:
//STATIC_ASSERT_NOT_COMPILES(MK_DEP(s).clear()); // Fails to compile, OK!
class C{}; // just an example class
C c;
STATIC_ASSERT_NOT_COMPILES(c=MK_DEP(7));
STATIC_ASSERT_NOT_COMPILES(7=MK_DEP(c));
STATIC_ASSERT_NOT_COMPILES(baz(foo(MK_DEP(7)=c)));
STATIC_ASSERT_NOT_COMPILES(MK_DEP(false)=1);
// What about constructing C from string?
STATIC_ASSERT_NOT_COMPILES(C(MK_DEP(std::string{})));
// assert fires: can add strings: OK!
//STATIC_ASSERT_NOT_COMPILES(MK_DEP(s)+cs+std::string());
// Too many arguments to MK_DEP is forced to give hard error: Fails to compile, OK!
// STATIC_ASSERT_NOT_COMPILES(MK_DEP(1,2,3)+1);
// Forgetting to add MK_DEP also gives a *hard* error. Fails to compile. OK!
// STATIC_ASSERT_NOT_COMPILES(7=c);
}
实现,依赖Yakk的test_invoke
和RETURNS
。欢迎反馈!
namespace details{
struct make_depend{
template<typename T> static constexpr const bool false_t = false;
template<typename T>
auto operator()(T&& arg) RETURNS(arg) ;
// Try to protect against wrong use: zero or many arguments:
template<typename T, typename... T2>
auto operator()(T&& arg, T2... too_many_arguments) {
static_assert(false_t<T>, "Too many arguments given to MK_DEP"); } ;
template<typename T=int>
auto operator()() { static_assert(false_t<T>, "Too few arguments given to MK_DEP"); } ;
};
}
#define STATIC_ASSERT_NOT_COMPILES(...)\
static_assert(!test_invoke([&](auto MK_DEP)RETURNS(__VA_ARGS__))\
(details::make_depend{}))
或者,一种不那么包装的方法:
#define CHECK_COMPILES(...)\
test_invoke([&](auto MK_DEP)RETURNS(__VA_ARGS__))(details::make_depend{})
static_assert(CHECK_COMPILES(cs=MK_DEP(s)));
甚至只是基本的想法:
static_assert(test_invoke([&](auto MK_DEP)RETURNS(s+MK_DEP(s)))(details::make_depend{}));
编辑: 可变参数 operator()
只是为了防止某些情况下错误使用 MK_DEP
。出于同样的原因,我还添加了一个 no-argument 版本。