2016-11-06 2 views
4

Betrachten Sie das folgende Beispiel:Trailing Rückgabetyp, Declval und Referenz Qualifier: Können sie zusammenarbeiten?

#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>(); 
} 

Wenn mit B (Linie (*)) aufgerufen, wird der Code kompiliert nicht mehr für std::declval wandelt T zu rvalue Referenztyp im konkreten Fall.
Wenn wir es leicht, als es ändern folgt, haben wir das entgegengesetzte Problem:

// ... 

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

// ... 

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

Jetzt Linie bei (*) nicht für std::declval wandelt den Typ Lvalue Referenztyp im konkreten Fall arbeiten.

Gibt es eine Möglichkeit, einen Ausdruck zu definieren, der den Typ T akzeptiert, wenn er eine Elementfunktion f hat, egal, was ist sein Referenzqualifikator?


Ich habe keinen realen Fall, in dem ich das verwenden würde, und ich kann kein wirkliches Beispiel für die Verwendung machen.
Diese Frage ist der Neugierde, nichts mehr.
Ich verstehe, dass es einen Grund gibt, wenn der Ref-Qualifier da ist und ich nicht versuchen sollte, das Design der Klasse zu brechen.

+0

Wie wäre es 'decltype (& T :: f)'? – Brian

+0

Funktioniert nicht für überladene 'f' – krzaq

+0

@Brian Nun, es würde in diesem minimalen Beispiel funktionieren, aber es funktioniert nicht, wenn Sie Argumente in der _invokation_ verwenden müssen. – skypjack

Antwort

2

eine Art Charakterzug bauen, die true zurück, wenn Ausdruck declval<T>().f(declval<Args>()...) ein gültiger Aufruf ist. Dann geben Sie U& und U&& ein und geben ein Lvalue- oder Rvalue-Objekt vom Typ T an.

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(); 

In C++ 17 dies wird es ein wenig einfacher:

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

Sie könnten Boost.Hana die Verwendung overload_linearly beide Varianten testen:

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

Verwandte Themen