C++ detecting free function existence with explicit parameters
Asked Answered
C

2

8

I'm writing some type traits to see if a free function exists with a specific set of parameters. The functions have a signature that looks something like this:

template <class T> void func( SomeClass &, SomeType const & );

I know ahead of time the values for T, SomeClass, and SomeType. I want the trait to return true if this function exists with exactly these parameters, not using any implicit conversion.

I can easily write some code to detect whether this function exists by using SFINAE to attempt to call it, e.g.

// potentially in some namespace
template <class> void func(); // this is necessary since user implementations
                              // of func will not exist until after 
                              // this has been defined
template <class X, class Y, class Z>
static auto test(int) -> 
  decltype( func<X>( std::declval<Y&>(), std::declval<Z const&>(), std::true_type());

template <class, class, class> static std::false_type test(...);

and appropriately testing the return types of those functions. Since I get to pass SomeClass (Y) to the function here, ADL can let the compiler look in the appropriate namespaces to not get confused by the dummy version of func I define for the tests.

The problem I run into here is that since the SomeType (Z in the above test) is passed by constant reference, it is allowed to implicitly convert to other types. For example, someone could define a function like: template <class T> void func( SomeClass &, double const & ); and for any arithmetic type for Z, my test will pass. I would like it to pass only when Z is the true type, in this case a double.

I tried to solve this by using function pointers in a scheme like the following:

// struct to prevent implicit conversion and enforce function signature
template <class Y, class Z>
struct NoConvert
{
  using FPType = void (*)(Y&, Z const &);
  explicit NoConvert( FPType );
};

template <class> void func(); // see note on why this is needed above

template <class X, class Y, class Z>
static auto test(int) -> decltype( NoConvert( &func<X> ), std::true_type() );
template <class, class, class>
static std::false_type test(...);

template <class X, class Y, class Z>
static bool value(){ return std::is_same<decltype(test<X, Y, Z>()), std::true_type>::value; }

In theory this would work great, but the problem I run into is that a later defined user version of func will not be seen by the test - it only sees the dummy func I need to define for the compiler to be happy. Unfortunately I don't get to pass the type SomeClass here so ADL can't kick in to get &func<X> to look up later defined user functions.

Is there any way I can accomplish this? The solution doesn't have to use function pointers, it just needs to be a trait that returns true if some free function exists with exactly a provided set of parameters.

For reference on desired behavior:

template <class T> void func( A &, int const & );

value<T, A, int>(); // return true
value<T, A, long>(); // return false
value<T, A, double>(); // return false
value<U, A, int>(); // return false
value<T, B, int>(); // return false
Cockrell answered 17/3, 2014 at 20:5 Comment(2)
Would something like this help? There's still some work to be done to get the references right (only allow certain conversions), but I think that can be done.Calci
I'd tried something similar to your any_t operator but didn't think of adding that enable_if. I think this is a very promising approach - will play around with it a bit.Cockrell
C
7

I was able to solve this using the following technique, thanks to help from dyp:

This solution no longer makes use of function pointers and instead relies on a proxy class that prevents implicit conversion:

template <class Source>
struct NoConvert
{
  template <class Dest, class = typename std::enable_if<std::is_same<Source, Dest>::value>::type>
  operator Dest () const = delete;

  template <class Dest, class = typename std::enable_if<std::is_same<Source, Dest>::value>::type>
  operator Dest const & () const;
};

template <class> void func();

template <class A, class T, class U>
static auto test(int) -> decltype( func<A>( std::declval<T&>(), NoConvert<U>() ), std::true_type() );

template <class, class, class>
static std::false_type test(...);

template <class A, class T, class U>
static bool valid()
{
  return std::is_same<decltype(test<A, T, U>(0)), std::true_type>::value;
}

which can be used like:

template <class T>
void func( B &, int const & );

template <class T>
void func( B &, std::string );

template <class T>
void func( A &, std::string const & );

std::cout << valid<A, B, int>() << std::endl;         // true
std::cout << valid<A, B, std::string>() << std::endl; // false
std::cout << valid<A, A, std::string>() << std::endl; // true

By playing with the conversion operators inside of NoConvert, you can make this work with passing by value, reference, or constant reference.

For example, in the current use, when the conversion operator for NoConvert<std::string> is triggered by a value parameter of std::string, both overloads are valid and thus there is ambiguity, meaning that SFINAE will cull this and allow the std::false_type test to pass. In the case of a constant reference parameter, the constant reference overload has precedence and properly allows the std::true_type test overload to pass.

This solution also relies on the ability to use ADL to resolve the name of the function, which was impossible with the function pointer approach.

Cockrell answered 17/3, 2014 at 22:36 Comment(0)
G
0

In C++20 this becomes fairly easy - here's a simple version if you have access to instances of the arguments:

constexpr bool check_func_arguments(SomeClass& a, SomeType const& b)
  auto check = [] (auto&&... args) { 
    return requires { func(args...); }; 
  };
  return check(a, b);
}
Gabbro answered 17/8, 2022 at 16:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.