“反SFINAE”如果给定的表达式*不是*格式良好,则启用重载

时间:2015-08-14 16:18:03

标签: c++ c++11 sfinae

如果特定表达式格式不正确,很容易使用SFINAE来隐藏特定的函数重载。但是我想做相反的事情,当且仅当给定的表达式 格式良好时才隐藏过载,并且以非常一般的方式这样做。我有一个在clang 3.5.0和gcc 5.2.0中工作的解决方案,但我对任何评论和替代方案感兴趣。

理想情况下,会有一个内置的constexpr bool函数/宏在编译时告诉我们特定表达式是否格式正确。

IS_WELL_FORMED(  declval<T>() < declval<T>()  )  // I want this as bool

可与enable_if一起使用以启用或禁用重载。

我找到了一个解决方案,但是我在g ++ 5.2.0和clang 3.5.0中遇到了一些奇怪的行为,我想知道是否有错误。

建议的解决方案

首先,我到目前为止找到的最强大的解决方案,适用于两个编译器。例如,我想测试T是否有.length()方法。这需要将表达式“隐藏”在另一个模板中。另外,我将在稍后讨论一个名为well_formed_default的函数。

    // Define a template to contain our expression
    template<typename T2=T, typename = 
        decltype( declval<T2>().length() ) // This line is the expression to test
    > struct test_for_length_method { };

以下是它在包含类中的使用方法:

template<typename T>
struct Demo { // the main struct I'm working on

    // Define a template to "hide" our expression
    template<typename T2=T, typename = 
        decltype( declval<T2>().length() ) // This line is the expression to test
    > struct test_for_length_method { };

    // test if the above "test" succeeds
    constexpr bool T_has_length =
        well_formed_default< test_for_length_method >();

    // demonstrate the bool in an enable_if
    template<bool b = T_has_length>
    static
    auto demo_enable_if() -> typename std::enable_if<  b >::type { 
        cout << "T has length" << endl;
    }
    template<bool b = T_has_length>
    static
    auto demo_enable_if() -> typename std::enable_if< !b >::type {
        cout << "T doesn't" << endl;
    }
}

上述内容与Demo<int>::demo_enable_if()Demo<std::string>::demo_enable_if()一样正常工作。

我无法在T_has_length内直接使用enable_if因为它不会导致硬错误,因为它不是模板替换。因此,我通过在另一个模板参数bool b = T_has_length中复制if来假装它是模板参数。这类似于我们在测试结构中使用typename T2=T的方式。有点烦人,但我想这很有意义。

我现在定义well_formed_default。它需要一个模板(以及可选的某些类型)并返回true或false,具体取决于它是否可以使用这些特定的args构造模板。我会在我的所有项目中自动包含它。也许这样的事情已经存在于标准中了吗?

template<template<class...> class Template, class ...Args>
constexpr auto well_formed_default_impl(int) 
    -> typename std::conditional<false,Template<Args...>, bool>::type {
        return true;
}
template<template<class...> class Template, class ...Args>
constexpr auto well_formed_default_impl(...)
    -> bool {
        return false; 
}   
template<template<class...> class Template, class ...Args>
constexpr bool well_formed_default() {
    return well_formed_default_impl<Template,Args...>(0);
}

-

我的(第一个)问题

这适用于g ++ 5.2.0和clang 3.5.0。但是应该吗?这是完全标准的,还是我推动标准太过分了?我想对我来说最奇怪的是在Template<Args...>内使用well_formed_default_impl - 这是否保证在我使用它的方式中取代失败?例如如果相关的test_for_pushback_method_struct<>格式不正确,请decltype

-

为什么我不能使用别名?

(对于这个问题的其余部分,可能有助于查看this code on Coliru的输出,因为它包含我在下面讨论的所有测试和结果。)

我用别名而不是上面的结构开始了这个项目。我认为这是等价的。但相反,对于两个编译器,他们认为string 有长度方法。

template<typename T2=T>
    using test_for_length_method_alias = decltype( declval<T2>().length() );

最后,我尝试了结构和别名,但我明确定义了第一个类型参数(T),而不是依赖于默认的T2=T。这不应该改变任何东西,因为我传递的是默认类型 - 但它确实改变了行为!使用带有显式第一个参数的结构

well_formed_default<test_for_length_method_struct , T>

在两个编译器上都能正常工作。但alias-with-explicit-first-type只适用于clang:

well_formed_default<test_for_length_method_alias , T>

2 个答案:

答案 0 :(得分:4)

对于直接支持这种用途的语言,不太可能有任何改变(除了概念带来的)。概念将使编写互斥的重载变得更容易,而否定的概念允许编写重载 如果表达式格式不正确,则启用。 (是的,我知道这不能帮助你为C ++ 11或C ++ 14编写这样的代码。)例如:

#include <iostream>

template <class T>
requires !requires(T t) {t.f();}
void f()
{
    std::cout << "does not support f()!" << std::endl;    
}

template <class T>
void f()
{
    std::cout << "supports f()!" << std::endl;    
}

struct A {};
struct B {void f() {}};
int main()
{
    f<A>();
    f<B>();
}

我添加了无约束的重载仅用于说明目的。 离开它会使调用f&lt; B&gt;()形成错误。该代码今天在gcc trunk上运行,您可以在melpon.org/wandbox上试用。

答案 1 :(得分:3)

这是can_apply样板:

template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

namespace details {
  template<template<class...>class Z, class, class...>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:
    std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;

对于x<y使用它的特定问题:

template<class Lhs, class Rhs>
using less_r = decltype( std::declval<Lhs>() < std::declval<Rhs>() );

template<class Lhs, class Rhs>
using can_less = can_apply< less_r, Lhs, Rhs >;

然后使用can_less

struct Foo {};
struct Bar {};

void operator<( Foo, Bar ) {}

int main() {
  std::cout << can_less<Foo, Bar>{} << can_less<Bar,Foo>{} << can_less<int, int>{} << can_less<char*, int>{} << '\n';
}

在clang和gcc中输出1010

这符合标准,它大致匹配std::experimental::is_detected的界面(功能较少,但实现起来更简单)。