Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 593
Tavian Barnes Avatar asked Feb 06 '23 17:02

Tavian Barnes


2 Answers

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.

like image 162
W.F. Avatar answered Feb 08 '23 05:02

W.F.


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 {};
like image 27
Guillaume Racicot Avatar answered Feb 08 '23 05:02

Guillaume Racicot