SFINAE to determine if a type has a potentially overloaded method

杀马特。学长 韩版系。学妹 提交于 2021-02-07 22:41:53

问题


I was looking for an SFINAE solution to check at compile time if a type has a method. My goal is to check if a type is a valid "duck type", but instead of a useless compile error, I want to use static_assert to provide an informative message.

I found [this question], which provides a fairly good answer to my problem, except it fails when the type provides overload to the method:

template<typename...> // parameter pack here
using void_t = void;

template<typename T, typename = void>
struct has_xxx : std::false_type {};

template<typename T>
struct has_xxx<T, void_t<decltype(&T::xxx)>> :
  std::is_member_function_pointer<decltype(&T::xxx)>{};

This works fine with the following example, and differentiate method and member variable:

struct Foo { int xxx() {return 0;}; };
struct Foo2 {};
struct Foo3{ static double xxx;};
double Foo3::xxx = 42;

int main() {
   static_assert(has_xxx<Foo>::value, "");
   static_assert(!has_xxx<Foo2>::value, "");
   static_assert(!has_xxx<Foo3>::value, "");
}

Original live demo

The code fails if there is an overload:

struct Foo { int xxx() {return 0;}  void xxx(int){} };

int main() {
   static_assert(has_xxx<Foo>::value, "");
}

Failing live demo with overloaded method

How can this code be improved to handle overloading?


回答1:


A proper duck type check is "can your xxx be invoked with a specific signature, and its return value used in a certain context". Having an overloaded xxx is not useful, because it matters how it is used.

We start with can_apply

namespace details {
  template<template<class...>class Z, class, class...>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
    std::true_type{};
}

template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;

which tells you if given arguments can be legally passed to some template.

We then express the duck type we want:

template<class T>
using xxx_result = decltype( std::declval<T>().xxx() );

template<class T>
using can_xxx = can_apply< xxx_result, T >;

and can_xxx<T> is truthy or falsy depending on if we can do a t.xxx() or not.

If we want a type restriction, we just:

template<class T, class R>
using xxx_result_as_R = decltype( R(std::declval<T>().xxx()) );
template<class T, class R>
using can_xxx_as_R = can_apply< xxx_result_as_R, T, R >;

so if you want xxx to return something int-able, we get:

template<class T>
using valid_xxx = can_xxx_as_R<T, int>;



回答2:


If all you're doing is checking if the name xxx exists within type T, then we can have the following approach that works for all non-final/union types T. We can create a new type that inherits publicly from both T and a fallback type that has a member xxx. We can access xxx unambiguously on the derived type if and only if T didn't have the member to begin with.

template <class T>
class has_xxx_impl
{
private:
    struct Fallback {
        int xxx;
    };

    struct Derived : T, Fallback { };

    template <class U>
    static std::false_type test(decltype(&U::xxx)* );
    template <class U>
    static std::true_type test(...);
public:
    using type = decltype(test<Derived>(nullptr));
};

template <class T>
struct has_xxx : has_xxx_impl<T>::type
{ };

If you want to check for more specific things - like for a T, you can call T{}.xxx(), I would do it differently.



来源:https://stackoverflow.com/questions/39750160/sfinae-to-determine-if-a-type-has-a-potentially-overloaded-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!