Вызов свободной функции вместо метода, если он не существует

Предположим, у вас есть семейство не связанных между собой классов, реализующих общую концепцию с помощью заданного метода, возвращающего значение:

class A { public: int val() const { ... } };
class B { public: int val() const { ... } };

предположим, что вам нужна общая свободная функция, возвращающая T стандартное значение для любого типа NOT, реализующего метод val, или вызов метода val для ВСЕХ типов, которые имеют один:

template<class T> int val_of(const T& t) { return 0; }
template<class T> int val_of(const T& t) { return t.val(); }

Учтите, что A и B являются просто образцами: вы не знаете, сколько типов когда-либо будет реализовано val, и сколько типов будет существовать, не реализуя его (поэтому явная специализация не будет масштабироваться).

Есть ли простой способ, основанный на стандартах С++, найти способ статически выбрать версию val_of?

Я думал о std::conditional или std::enable_if, но я не нашел простого способа выразить это условие.

Ответы

Ответ 1

Несколько более длинный комментарий... На ваш вопрос был дан ответ. Но у меня недавно была аналогичная проблема. Предположим, что вы хотите написать метод для печати строк в cout: Использовать функцию-член write(std::cout), если она недоступна, используйте свободную функцию to_string(), если она недоступна для возврата в operator<<. Вы можете использовать выражение SFINAE, как в ответе, и немного иерархии классов, чтобы устранить перегрузку:

struct S3 {};
struct S2 : S3 {};
struct S1 : S2 {};

template <class T>
auto print(S1, T const& t) -> decltype(t.write(std::cout)) {
    t.write(std::cout);
}

template <class T>
auto print(S2, T const& t) -> decltype(std::cout << to_string(t)) {
    std::cout << to_string(t);
}

template <class T>
void print(S3, T const& t) {
    std::cout << t;
}

template <class T>
void print(T const& t) {
    print(S1(), t);
}

Ответ 2

Вы можете использовать SFINAE типа возвращаемого типа:

template<typename T>
auto val_of(const T& t) -> decltype(std::declval<T>().val())
{
    return t.val();
}

int val_of(...)
{
    return 0;
}

Ответ 3

Используйте класс этого типа, чтобы определить, получил ли тип val функцию-член.

template<typename U>
struct has_val {
    typedef char yes;
    typedef struct { char c[2]; } no;

    template<typename X>
    static constexpr auto test(int) -> decltype( std::declval<X>().val(), yes() );

    template<typename X>
    static constexpr no test(...);

    static constexpr bool value = sizeof(test<U>(0)) == sizeof(yes);
};

Вы можете использовать его как условие для enable_if.

Ответ 4

В настоящее время самый высокий проголосовавший ответ вызывает поведение undefined в некоторых случаях, поэтому я дам альтернативный ответ.

Начнем с некоторых машин:

template<typename T> struct type_sink { typedef void type; }
template<typename T> using TypeSink = typename type_sink<T>::type;

Затем класс has_val:

template<typename T, typename=void>
struct has_val : std::false_type;
template<typename T>
struct has_val<T, TypeSink< decltype(std::declval<T>().val()) > > : std::true_type;

Затем мы можем использовать диспетчер тегов для решения вашей проблемы:

template<typename T>
int val_of_internal( T const& t, std::true_type /* has_val */ ) {
  return t.val();
}
template<typename T>
int val_of_internal( T const& t, std::false_type /* has_val */ ) {
  return 0;
}
template<typename T>
int val_of( T const& t ) {
  return val_of_internal( t, has_val<T const>() );
}

Если вы находите запись has_val утомительной и предпочитаете макросы, вот набор макросов, которые пишут has_val для вас:

#define EXPRESSION_IS_VALID_FOR_TYPE_T_TRAIT( TRAIT_NAME, ... ) \
template<typename T, typename=void> \
struct TRAIT_NAME : std::false_type {}; \
template<typename T> \
struct TRAIT_NAME< T, TypeSink< decltype( __VA_ARGS__ ) > >: std::true_type {}

#define HAS_NULLARY_METHOD_TRAIT(TRAIT_NAME, METHOD_NAME) \
EXPRESSION_IS_VALID_FOR_TYPE_T_TRAIT( TRAIT_NAME, std::declval<T>().METHOD_NAME() )

Теперь мы можем написать это:

HAS_NULLARY_METHOD_TRAIT( has_val, val );

но я не знаю, стоит ли это.