1

At the moment, I have the below working code where using class X, I provide a generic interface for multiple classes - I only expect a static f function to be present, but neither do I fix the return type nor the parameters:

#include <iostream>
#include <type_traits>

class A {
public:
  static void f()
  {
    std::cout << "A::f()" << std::endl;
  }
};

class B {
public:
  static size_t f(std::string const& s_)
  {
    std::cout << "B::f()" << std::endl;
    return s_.size();
  }
};

class C {
};


template <typename T>
class X {
public:
  static
  void
  f(...)
  {
    std::cout << "Operation not supported!" << std::endl;
  }

  template <typename... Args>
  static
  std::result_of_t<decltype(&T::f)(Args...)>
  f(Args&&... args_)
  {
    return T::f(std::forward<Args>(args_)...);
  }
};

int main()
{
  X<A>::f();  // Compiles, but unexpected overload!
  auto const x = X<B>::f("Hello");  // Works just fine
  std::cout << x << std::endl;
//  X<C>::f();  // Does NOT compile!
  return 0;
}

However, I've got multiple problems with it (as marked above in the comments):

  1. If I allow the function to be not present and uncomment the line using C as a template parameter, the code does not compile, it still expects C to have a function f:

    In instantiation of ‘class X<C>’:
    required from here
    error: ‘f’ is not a member of ‘C’
    std::result_of_t<decltype(&T::f)(Args...)>
    

    Based on this, I expected the ellipsis parameter do the trick.

  2. On the other hand, even if this worked, I would have another problem: though A provides f, the overload with the ellipsis parameter is picked during overload resolution - where, of course, f provided by A is preferred.

(Used compiler: g++ (Ubuntu 8.1.0-5ubuntu1~16.04) 8.1.0; used standard: C++14.)

Any help solving the above problems (preferably both, at the same time) is welcomed.

mindthegap
  • 323
  • 2
  • 7
  • 19

1 Answers1

3

I propose the following X struct

template <typename T>
struct X
 {
  static void g (...)
   { std::cout << "Operation not supported!" << std::endl; }

  template <typename ... As, typename U = T>
  static auto g(int, As && ... as)
     -> decltype( U::f(std::forward<As>(as)...) )
   { return T::f(std::forward<As>(as)...); }

  template <typename ... As>
  static auto f (As && ... as)
   { return g(0, std::forward<As>(as)...); }
 };

The points are

(1) Passing through a g() function with an additional unused parameter (int in the variadic version, adsorbed by ... in the "not supported version"), permit to select the variadic version (called with 0 that is an int), when sizeof...(As) == 0 and both g() functions are available. This because int is a better match (in an exact match) for int as .... But when the variadic version isn't available (see point (2)), the "non supported" version is still available (the only one available) and selected

(2) You have to SFINAE enable/disable the variadic version according the fact that the f() static method is (or ins't) available in T. Unfortunately T is a template argument of the class, not of the g() static method, so you can use it for SFINAE. The trick is SFINAE operate over an additional template parameter, U, that is defaulted to T

// ------------------------VVVVVVVVVVVVVV  additional U (defaulted to T) template type
template <typename ... As, typename U = T>
static auto g(int, As && ... as)
   -> decltype( U::f(std::forward<As>(as)...) )
// -------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// SFINAE enable this g() if (and only if) the call
//   U::f(std::forward<As>(as)...) is valid    

The following is a simplified working example

#include <iostream>
#include <type_traits>

struct A
 { static void f() { std::cout << "A::f()" << std::endl; } };

struct B
 {
  static size_t f(std::string const& s_)
   { std::cout << "B::f()" << std::endl; return s_.size(); }
 };

struct C
 { };


template <typename T>
struct X
 {
  static void g (...)
   { std::cout << "Operation not supported!" << std::endl; }

  template <typename ... As, typename U = T>
  static auto g(int, As && ... as)
     -> decltype( U::f(std::forward<As>(as)...) )
   { return T::f(std::forward<As>(as)...); }

  template <typename ... As>
  static auto f (As && ... as)
   { return g(0, std::forward<As>(as)...); }
 };

int main ()
 {
   X<A>::f();  // now call variadic version
   auto const x = X<B>::f("Hello");  // Works just fine as before
   std::cout << x << std::endl;
   X<C>::f();  // now compile
 }
max66
  • 65,235
  • 10
  • 71
  • 111