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 提出了 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。) 具有相似的特征;它可以代替使用。

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,这是一个 特性,但还是一个早期的特性。没有它也可以完成,但它会变得丑陋。加上省略的 move ctor 要求在 .

中变通很烦人

转换为 ,将每个 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_invokeRETURNS。欢迎反馈!

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{}));

Compiler explorer demo

编辑: 可变参数 operator() 只是为了防止某些情况下错误使用 MK_DEP。出于同样的原因,我还添加了一个 no-argument 版本。