基于当前类中的可用重载,有没有办法让SFINAE?

时间:2016-11-14 18:10:08

标签: c++ c++11 templates clang sfinae

我一直在使用这样的代码(至少从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.

有没有办法实现这一点,而不依赖于在自己的定义中使用不完整的类型?

4 个答案:

答案 0 :(得分:4)

也许你可以让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完成,这会延迟检查。

答案 1 :(得分:2)

快速简便的方法是在基类中定义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 {};

答案 2 :(得分:1)

看起来你正试图让它成为如果函数的主体无法编译就不会选择重载。问题是编译器需要确保签名在转移到正文之前编译。

相反,基于你需要能够更具体地使用R的内容来处理SFINAE?例如:

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

这样,只有在f begin类型上调用endconst R&时才会选择此重载。

答案 3 :(得分:1)

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