我一直在使用这样的代码(至少从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.
有没有办法实现这一点,而不依赖于在自己的定义中使用不完整的类型?
答案 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);
};
如果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
类型上调用end
和const 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)。