I've been using code like this for a while (since GCC 4.9/Clang 3.5 at least):
#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);
};
The point of the second bar()
overload is it should be SFINAE'd away unless R
is a range type where an overload of bar()
exists for its elements. So std::vector<int>
would be fine but std::vector<int*>
wouldn't, for example.
Unfortunately, since Clang 3.9, that gives this error:
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.
Is there a way to accomplish this that doesn't rely on using an incomplete type from within its own definition?
Maybe you could make foo a default value of additional template parameter:
#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]
This would delay the check if foo
is complete.
The fast and easy way would be to define bar
in a base class.
#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);
};
But this method can be cumbersome.
Alternatively, if you know what type bar
need, you can do this:
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);
};
If bar
is limited with a constraint, you could use the very same constraint:
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);
};
At last, if you like the last two options, you can encapsulate the range constraint in a type trait:
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 {};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With