2017-06-06 1 views
16

Die Detektions Idiom funktioniert wie folgtWarum ist sfinae eingeschaltet, wenn conexpr nicht erlaubt ist?

template<typename T, typename = void> 
struct has_foo {static constexpr bool value = false;}; 
template<typename T> 
struct has_foo<T, std::void_t<decltype(&T::foo)>> {static constexpr bool value = true;}; 
template<typename T> 
constexpr bool has_foo_v = has_foo<T>::value; 

Und dann können wir das Vorhandensein von foo in jeder Art T erkennen.

if constexpr(has_foo_v<decltype(var)>) 
    var.foo(); 

Mein Problem ist, ist, dass eine ganze Menge (sprich: Will ich meine Tastatur viel zu geben zu zerschlagen) zu geben, und ich fragte mich, ob folgend möglich ist

if constexpr(std::void_t<decltype(&decltype(var)::foo)>(), true) 
    var.foo(); 

Es ist nicht .

Gibt es einen Grund dafür?
Genauer gesagt, welche Kompromisse müssen gemacht werden, wenn dies erlaubt wäre?

+9

[Maßgebliche] (https://meta.stackoverflow.com/a/323382/2069064). Eine Antwort könnte sein, dass es hier keine Substitution gibt, also wie kann es SFINAE geben? Eine andere Antwort könnte darin bestehen, dass sie nicht berücksichtigt wurde, da es sich bei den Vorschlägen immer um das 'if' handelte, das einen' Bool' nahm? – Barry

+0

@Barry Hoffnungsvoll bearbeitete Frage in eine Form, die eine klare Antwort haben kann. War ursprünglich über etwas nach dem Motto [diese Art von Antwort] (https://stackoverflow.com/a/6623089/4832499) –

Antwort

6

Ihre Nutzung von Zeigern auf Elementfunktion ist eine schlechte Idee; Wenn foo überlastet ist, scheitert es fälschlicherweise (Sie haben ein foo, aber nicht nur eins). Wer will wirklich "hast du genau ein Foo"? Fast niemand.

Hier ist eine kürzere Version:

template<class T> 
using dot_foo_r = decltype(std::declval<T>().foo()); 

template<class T> 
using can_foo = can_apply<dot_foo_r, T>; 

wo

namespace details { 
    template<template<class...>class, 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...>; 

Nun dot_foo_r ein bisschen ärgerlich ist das Schreiben.

Mit constexpr lambdas können wir es weniger lästig machen und es inline machen.

#define RETURNS(...) \ 
    noexcept(noexcept(__VA_ARGS__)) \ 
    -> decltype(__VA_ARGS__) \ 
    { return __VA_ARGS__; } 

Es braucht die RETURNS Makro, zumindest bis @ Barrys Unterwerfung unter [](auto&&f)RETURNS(f()) zu [](auto&&f)=>f() gleichwertig sein.

Wir haben dann can_invoke_f schreiben, die eine ist constexpr Variante std::is_invokable:

template<class F> 
constexpr auto can_invoke(F&& f) { 
    return [](auto&&...args)->std::is_invokable<F(decltype(args)...)>{ 
    return {}; 
    }; 
} 

Das gibt uns:

if constexpr(
    can_invoke([](auto&&var) RETURNS(var.foo()))(var) 
) { 
    var.foo(); 
} 

oder mit @ Barry vorgeschlagene C++ 20-Syntax:

if constexpr(can_invoke(var=>var.foo())(var)) { 
    var.foo(); 
} 

und wir sind fertig.

Der Trick ist, dass RETURNS Makro (oder => C++ 20 Feature) lässt uns SFINAE auf einen Ausdruck tun. Das Lambda wird ein einfacher Weg, um diesen Ausdruck als Wert herumzutragen.

Sie könnten

[](auto&&var) ->decltype(var.foo()) { return var.foo(); } 

schreiben, aber ich denke, RETURNS es wert ist (und Ich mag Makros nicht).

+2

War gerade dabei, diesen Fall mit einzubeziehen als eine andere Art von motivierendem Beispiel im nächsten Entwurf. 'wenn conexpr (can_invoke_f (x => x.foo())) {...}' immer noch nicht genau ideal im Vergleich zu dem, was OP wollte, aber es ist nicht schlecht. – Barry

+0

@Barry Ein "declltype" und ein verbessertes "can_invoke" wurden entfernt; 'can_invoke' nimmt nun eine Funktionsinstanz' f' und gibt auf 'f' einen' constexpr' Invoke-Tester zurück. Freel frei zu stehlen/anzupassen; Ich denke, es ist jetzt viel geschmeidiger. – Yakk

+3

Was ist die '=>' Syntax? –

12

Seit C++ 17 immer ein constexpr Lambda-Problem umgehen, wenn Sie wirklich sfinae Inline tun müssen, um:

#include <utility> 

template <class Lambda, class... Ts> 
constexpr auto test_sfinae(Lambda lambda, Ts&&...) 
    -> decltype(lambda(std::declval<Ts>()...), bool{}) { return true; } 
constexpr bool test_sfinae(...) { return false; } 

template <class T> 
constexpr bool bar(T var) { 
    if constexpr(test_sfinae([](auto v) -> decltype(v.foo()){}, var)) 
     return true; 
    return false; 
} 

struct A { 
    void foo() {} 
}; 

struct B { }; 

int main() { 
    static_assert(bar(A{})); 
    static_assert(!bar(B{})); 
} 

[live demo]

+0

Beachten Sie, dass 'declval ()' ist ein lvalue Verweis, also wenn die Funktion, die Sie testen akzeptiert keine nicht konstante Lvalue-Referenz, der Test wird fehlschlagen. –

+0

@IgorR. Du hast Recht, aber wir haben den Vorteil, dass wir das Lambda 'test_sfinae' testen werden. Trotzdem muss ich darüber nachdenken, denn es ist alles andere als perfekt. –

+1

@IgorR. Ich habe die Referenz in 'declval' entfernt, jetzt liegt es an Lambda Creator, ob er auf Referenzen oder auf Werte operieren möchte. Wenn der Kopierkonstruktor explizit gelöscht wird, passt Lambda nur, wenn eine Referenz akzeptiert wird, zB '[] (auto & v)', '[] (const auto & v)' oder '[] (auto && v)' –

1

Sie können auch die Menge an Code reduzieren, indem Sie std::is_detected verwenden.

In Ihrem Beispiel würde der Code dann wie folgt aussehen:

template <class T> 
using has_foo_t = decltype(std::declval<T>().foo()); 

if constexpr(std::is_detected_v<has_foo_t,decltype(var)>) 
    var.foo(); 
+0

Es funktioniert! :) Sind Sie sicher, dass dies C++ 17 Feature ist? –

+1

@ W.F .: Es ist ein experimentelles Feature. Es ist noch nicht standardisiert. – AndyG

Verwandte Themen