有没有一种方法可以根据当前 class 中的可用重载来进行 SFINAE?

Is there a way to SFINAE based on available overloads in the current class?

我使用这样的代码已经有一段时间了(至少从 GCC 4.9/Clang 3.5 开始):

#include <utility>

class foo
{
public:
    void bar(int n);

    template <typename R,
              typename = decltype(std::declval<foo>().bar(*std::begin(std::declval<R>())))>
    void bar(const R& range);
};

第二个 bar() 重载的要点是它应该被 SFINAE 移除,除非 R 是一个范围类型,其中的元素存在 bar() 的重载。所以 std::vector<int> 可以,但是 std::vector<int*> 就不行,例如。

不幸的是,从 Clang 3.9 开始,出现了这个错误:

templ.cpp:12:54: error: member access into incomplete type 'foo'
              typename = decltype(std::declval<foo>().bar(*std::begin(std::declval<R>())))>
                                                     ^
templ.cpp:6:7: note: definition of 'foo' is not complete until the closing '}'
class foo
      ^
1 error generated.

有没有一种方法可以实现此目的而不依赖于使用其自身定义中的不完整类型?

快速简便的方法是在基数 class 中定义 bar

#include <utility>

template<typename child>
struct base {
    void bar(int);
};

struct foo : base<foo> {
    template<typename R,
              typename = decltype(std::declval<base<foo>>().bar(std::begin(std::declval<R>())))>
    void bar(const R& range);
};

但是这个方法比较麻烦。

或者,如果您知道 bar 需要什么类型,您可以这样做:

struct foo {
    void bar(int);

    template<typename R,
        std::enable_if_t<std::is_constructible<int, decltype(*std::begin(std::declval<R>()))>>* = 0>
    void bar(const R& range);
};

如果 bar 受约束限制,您可以使用完全相同的约束:

struct foo {
    template<typename T, std::enable_if_t<some_contraint<T>::value>* = 0>
    void bar(T);

    template<typename R,
        std::enable_if_t<some_contraint<*std::begin(std::declval<R>())>::value>* = 0>
    void bar(const R& range);
};

最后,如果您喜欢最后两个选项,可以将范围约束封装在类型特征中:

template<typename, typename = void>
struct is_valid_range : std::false_type {};

template<typename T>
struct is_valid_range<T, std::enable_if_t<some_contraint<*std::begin(std::declval<R>())>::value>> : std::true_type {};

看起来您正在尝试这样做,以便在函数体无法编译时不会选择重载。问题是编译器需要确保在移动到正文之前编译签名。

相反,如果 SFINAE 更具体地基于您需要能够用 R 做什么呢?例如:

template<typename R,
         class = decltype(begin(std::declval<const R&>())),
         class = decltype(end(std::declval<const R&>()))>
void bar(const R& range);

只有当您可以在类型 f const R&.

上调用 beginend 时才会选择此重载
class Foo;
void free_bar(Foo* foo, int n){
  (void)foo;
  std::cout << n << "\n";
}

class Foo {
public:
  template<class X>
  void bar(X&& x) {
    return free_bar( this, std::forward<X>(x) );
  }
};

template <typename R>
auto free_bar(Foo* foo, const R& range)
-> decltype( free_bar( foo, *std::begin(range) ) )
{
  for (auto&&x:range)
    free_bar(foo, decltype(x)(x));
}

这会将 bar 放入以 Foo* 作为第一个参数的自由函数中。

会员.bar(X)调用了这个免费功能。

ADL 意味着它通常做正确的事(tm)。

live example

也许您可以将 foo 设置为附加模板参数的默认值:

#include <utility>

class foo
{
public:
    void bar(int n);

    template <typename R,
              typename F = foo,
              typename = decltype(std::declval<F>().bar(*std::begin(std::declval<R>())))>
    void bar(const R& range);
};

[live demo]

如果 foo 完成,这将延迟检查。