9

Consider an overloaded function

void f(int);
void f(long);
void f(double);
void f(MyClass);

And a method in a template class with unknown argument type

template <class T>
struct C {
  void method(T arg) { ... }
};

I want to check at compile time if there is a version of f which can take arg as an argument.

template <class T>
struct C {
  void method(T arg) { 
    if constexpr (CAN_BE_CALLED(f, arg)) {
      f(arg);
    } else {
      g();
    }
  }
};

Is it possible to do that? I tried this and this but compiler complained about unresolved overloaded function type.

5r1gsOi1
  • 118
  • 1
  • 7
  • 3
    At the very least, that `if` will need to be a `constexpr if`. If `f(arg);` is illformed, the compiler would complain of it's presence even if the branch could never be taken. Using `constexpr if` fixes that. – François Andrieux Jul 18 '18 at 14:35
  • Is it possible to add a function f() that takes any type and that calls g(). The compiler would do the work for you. I'm not sure how to do this. May be void f(...){g();} ? – William J Bagshaw Jul 18 '18 at 14:51
  • @WilliamJBagshaw `template void f(T&&) { g(); }` sounds like what you are suggesting. But then it gets tricky when you call with `int&` or other almost-but-not-quite-the-same arguments. – François Andrieux Jul 18 '18 at 14:54
  • @Mat: `is_invocable` is a little problematic to call with overloads... – Jarod42 Jul 18 '18 at 15:09
  • If you struggle create your own test as a templated function and populate the specialisations. Default template returns false. Available types return true. Then I think you could also add false positives if you want them to be false. You would have to keep it in sync yourself and getting all false positive may be an issues. But it looks like automatic ways are difficult too. – William J Bagshaw Jul 18 '18 at 16:20

2 Answers2

6

You could use the detection idiom to build such a test

template<typename = void, typename... Args>
struct test : std::false_type {};

template<typename... Args>
struct test<std::void_t<decltype(f(std::declval<Args>()...))>, Args...>
    : std::true_type {};

template<typename... Args>
inline constexpr bool test_v = test<void, Args...>::value;

And use it as

template <class T>
struct C
{
    void method(T arg)
    { 
        if constexpr (test_v<T>)
            f(arg);
        else
            g();
    }
};

Live

Or alternatively

template<typename... Args>
using test_t = decltype(f(std::declval<Args>()...));

template<typename... Args>
inline constexpr auto test_v = std::experimental::is_detected_v<test_t, Args...>;
Passer By
  • 19,325
  • 6
  • 49
  • 96
  • Possibly [std::experimental::is_detected](https://en.cppreference.com/w/cpp/experimental/is_detected). – Jarod42 Jul 18 '18 at 15:08
  • @Jarod42 I was thinking about it, but am unsure how widely available/accepted it is. – Passer By Jul 18 '18 at 15:10
  • It doesn't work with overloaded functions in GCC =\. But it works if write these templates *after* function declarations, which is rarely acceptable. But in MSVC or without overloaded functions works well. – Ihor Drachuk Oct 13 '21 at 19:52
4

You might do the following with SFINAE:

template <class T, typename Enabler = void>
struct C {
    void method(T arg) {
        g();
    }
};

template <class T>
struct C<T, std::void_t<decltype(f(std::declval<T>()))>> {
    void method(T arg) { 
        f(arg);
    }
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302