2016-11-06 6 views
4

Рассмотрим следующий пример:Обратный тип возврата, декларационные и контрольные квалификаторы: могут ли они работать вместе?

#include <utility> 

struct A { void f() {} }; 
struct B { void f() & {} }; 
struct C { void f() && {} }; 

template<typename T> 
auto f() -> decltype(std::declval<T>().f()) 
{} 

int main() { 
    f<A>(); 
    // f<B>(); // (*) 
    f<C>(); 
} 

При вызове с B (строка (*)), код не компилируется больше для std::declval преобразует T к опорным Rvalue типа в конкретном случае.
Если мы изменим его немного, как это следует, мы имеем противоположную проблему:

// ... 

template<typename T> 
auto f() -> decltype(std::declval<T&>().f()) 
{} 

// ... 

int main() { 
    f<A>(); 
    f<B>(); 
    // f<C>(); // (*) 
} 

Теперь линия (*) не будет работать для std::declval преобразующего типа к Lvalue ссылочного типа в конкретном случае.

Есть ли способ определить выражение, которое принимает тип T, если он имеет функцию-член f, независимо от того, каков его ссылочный классификатор?


У меня нет никакого реального дела, в котором я хотел бы использовать это, и я не могу делать какие-либо реальный пример использования.
Этот вопрос ради любопытства, не более того.
Я понимаю, что есть причина, если есть ref-qualifier, и я не должен пытаться сломать дизайн класса.

+0

Как насчет 'decltype (& T :: е)'? – Brian

+0

Не будет работать для перегруженных 'f' – krzaq

+0

@Brian Ну, это сработает в этом минимальном примере, но это не сработает, если у вас есть аргументы, которые будут использоваться в _invokation_. – skypjack

ответ

2

Построить тип признака, который возвращает истину, если выражение declval<T>().f(declval<Args>()...) является действительным вызовом. Затем перейдите в U& и U&&, указав объект lvalue или rvalue типа T.

namespace detail{ 
    template<class...>struct voider{using type=void;}; 
    template<class... Ts>using void_t=typename voider<Ts...>::type; 

    template<template<class...> class, class=void, class...> 
    struct can_apply : false_type { }; 

    template<template<class...> class L, class... Args> 
    struct can_apply<L, void_t<L<Args...>>, Args...> : true_type {}; 

    template<class T> 
    using rvalue = decltype(declval<T>().f()); 
    template<class T> 
    using lvalue = decltype(declval<T&>().f()); 

    template<class T> 
    using can_apply_f 
    = integral_constant<bool, detail::can_apply<rvalue, void_t<>, T>{} || 
           detail::can_apply<lvalue, void_t<>, T>{}>; 
} 

template<class T> 
enable_if_t<detail::can_apply_f<T>{}> 
f(); 

В C++ 17 это становится немного проще:

namespace detail{ 
    auto apply=[](auto&&g,auto&&...xs)->decltype(decltype(g)(g).f(decltype(xs)(xs)...),void()){}; 

    template<class T> 
    using ApplyF = decltype(apply)(T); 

    template<class T> 
    using can_apply_f = std::disjunction<std::is_callable<ApplyF<T&>>, std::is_callable<ApplyF<T&&>>>; 
} 
0

Вы можете использовать Boost.Hana-х overload_linearly, чтобы проверить оба варианта:

template<typename T> 
using lval_of = add_lvalue_reference_t<decay_t<T>>; 

auto call_f = hana::overload_linearly(
    [](auto&& t) -> decltype(move(t).f()){}, 
    [](auto&& t) -> decltype(declval<lval_of<decltype(t)>>().f()){} 
); 

template<typename T> 
auto f() -> decltype(call_f(declval<T>())) 
{} 

demo