2017-06-08 3 views
20

In MSVC2017 das funktioniert gut, beide static_asserts nicht wie erwartet ausgelöst:Warum funktioniert dieses SFINAE-Snippet nicht in g ++, funktioniert aber in MSVC?

template <typename T> 
struct do_have_size { 
    template <typename = decltype(std::declval<T>().size())> 
    static std::true_type check(T); 
    static std::false_type check(...); 
    using type = decltype(check(std::declval<T>())); 
}; 

int main() { 
    using TR = typename do_have_size<std::vector<int>>::type; 
    using FL = typename do_have_size<int>::type; 

    static_assert(std::is_same<TR, std::true_type>::value, "TRUE"); 
    static_assert(std::is_same<FL, std::false_type>::value, "FALSE"); 
} 

Wenn ich jedoch in g ++ 7.1 oder Klirren 4.0 Ich erhalte folgende Compiler-Fehler kompilieren:

In instantiation of 'struct do_have_size<int>': 
20:39: required from here 
9:24: error: request for member 'size' in 'declval<do_have_size<int>::TP>()', which is of non-class type 'int' 

Aus mein Verständnis von SFINAE, Ersatz der true_type Rückgabe-Funktion sollte für int Parameter fehlschlagen und nächste Funktion gewählt werden, wie es in MSVC getan wird. Warum erstellen clang und g ++ überhaupt nichts?

Ich habe mit -std=c++17 nur Switch kompiliert, vielleicht ist mehr nötig?

+3

Wenn Sie C++ 17 verwenden, fragen Sie nach C++ 11. – Yakk

+1

Ich kann nicht sicher herausfinden, ob 'is_detected' es in C++ 17 geschafft hat. Wenn dem so ist, scheint das perfekt dafür zu sein. Und selbst wenn nicht, sollte die Implementierung von 'std :: experimental :: is_detected' bei cppreference.com es einfach machen. –

Antwort

12

Das hat absolut nichts damit zu tun, ob Standardschablonenargumente Teil einer Signatur einer Funktionsschablone sind.

Das eigentliche Problem ist, dass T eine Klasse Template-Parameter ist, und wenn Sie die Klasse-Vorlage Definition instanziiert, kann die Implementierung sofort ersetzen sie in Ihrem Standard-Template-Argument, decltype(std::declval<T>().size()) außerhalb des Abzugs Template-Argument, die einen harten Fehler verursacht, wenn size ist nicht vorhanden.

Die Lösung ist einfach; machen Sie es einfach abhängig von einem Parameter der Funktionsvorlage.

template <typename U, typename = decltype(std::declval<U>().size())> 
static std::true_type check(U); 

(Es gibt andere Probleme bei der Implementierung, wie es erfordert Move-konstruierbar nicht abstrakt T und nicht size() erfordert const-aufrufbar zu sein, aber sie sind nicht die Ursache des Fehlers Sie sehen.)

23

SFINAE funktioniert hier nicht, da die Klasse bereits mit T = int in do_have_size<int>::type instanziiert ist. SFINAE funktioniert nur für eine Liste der Template-Funktion Kandidaten, in Ihrem Fall, dass Sie da in der Instanziierung für int

do_have_size<int>::type 

die Memberfunktion

template <typename = decltype(std::declval<int>().size())> 
static std::true_type check(T); 

ist sicherlich schlecht gebildet einen harten Fehler bekommen. Die

static std::false_type check(...); 

wird nie in Betracht gezogen werden. So gcc ist hier, wenn Sie Ihren Code ablehnen und MSVC2017 sollte den Code nicht annehmen.

Verwandte: std::enable_if : parameter vs template parameter und SFINAE working in return type but not as template parameter

Eine Lösung ist die Magie von void_t zu verwenden (da C 17 ++, aber Sie können Ihre eigene in C++ 14.11 definieren), die jede Art abbildet Liste zu void und ermöglicht verrückt einfach aussehende SFINAE Techniken, wie so

#include <utility> 
#include <vector> 

template<typename...> 
using void_t = void; // that's how void_t is defined in C++17 

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

template <typename T> 
struct has_size<T, void_t<decltype(std::declval<T>().size())>> 
    : std::true_type {}; 

int main() { 
    using TR = typename has_size<std::vector<int>>::type; 
    using FL = typename has_size<int>::type; 

    static_assert(std::is_same<TR, std::true_type>::value, "TRUE"); 
    static_assert(std::is_same<FL, std::false_type>::value, "FALSE"); 
} 

Live on Wandbox

Here ist ein Cpp con video von Walter Brown, das die void_t Techniken im Detail erklärt, empfehle ich es sehr!

+0

/seufzen. "Daher wird SFINAE in diesem Fall nicht funktionieren, mit anderen Worten, es gibt keine Substitution/Verwerfung", ist nur Unsinn. –

+0

@ T.C Ich erkannte, dass es irreführend war, glücklich jetzt? – vsoftco

+0

"Standardschablonenargumente sind nicht Teil der Funktionssignatur" hat immer noch nichts mit dem OP-Problem zu tun, und die vorgeschlagene Korrektur mit 'is_fundamental' ist weder hier noch dort: es prüft überhaupt nicht auf' size' und es ist völlig unnötig, wenn die Überprüfung ordnungsgemäß durchgeführt wird. –

2

Ich habe es zu arbeiten, indem Sie std::enable_if zu SFINAE weg die Vorlage Version der Prüfung basierend auf entweder den Parameter oder den Rückgabetyp. Die Bedingung, die ich verwendete, war std::is_fundamental, um int, float und andere Nicht-Klassen-Typen davon auszuschließen, die Vorlage zu instanziieren. Ich benutzte die -std=c++1z Flagge zu clang und gcc. Ich erwarte -std=c++14 sollte auch funktionieren.

#include <type_traits> 
#include <utility> 
#include <vector> 

template <typename T> 
struct do_have_size { 
    static std::false_type check(...); 

    template <typename U = T, typename = decltype(std::declval<U>().size())> 
    static std::true_type check(std::enable_if_t<!std::is_fundamental<U>::value, U>); 

    // OR 
    //template <typename U = T, typename = decltype(std::declval<U>().size())> 
    //static auto check(U) 
    // -> std::enable_if_t<!std::is_fundamental<U>::value, std::true_type>; 

    using type = decltype(check(std::declval<T>())); 
}; 

int main() { 
    using TR = typename do_have_size<std::vector<int>>::type; 
    using FL = typename do_have_size<int>::type; 

    static_assert(std::is_same<TR, std::true_type>::value, "TRUE");  
    static_assert(std::is_same<FL, std::false_type>::value, "FALSE"); 
} 
5

@vsoftco antwortete "gcc ist richtig, um Ihren Code abzulehnen". Ich stimme zu.

zu beheben, ich sage nicht:

namespace details { 
    template<template<class...>class Z, class, class...Ts> 
    struct can_apply:std::false_type{}; 
    template<class...>struct voider{using type=void;}; 
    template<class...Ts>using void_t = typename voider<Ts...>::type; 

    template<template<class...>class Z, class...Ts> 
    struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{}; 
} 
template<template<class...>class Z, class...Ts> 
using can_apply=details::can_apply<Z,void,Ts...>; 

Dies ist eine can_apply Bibliothek, die diese Art von SFINAE einfach macht.

Jetzt ist einer dieser Züge zu schreiben ist so einfach wie:

template<class T> 
using dot_size_r = decltype(std::declval<T>().size()); 

template<class T> 
using has_dot_size = can_apply< dot_size_r, T >; 

Testcode:

int main() { 
    static_assert(has_dot_size<std::vector<int>>{}, "TRUE"); 
    static_assert(!has_dot_size<int>{}, "FALSE"); 
} 

Live-Beispiel.

In C++ 17 können Sie zu weniger mit declval gefüllten Ausdrücken wechseln.

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

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

can_invoke nimmt eine Funktion f und gibt eine "Invokation Tester". Der Invokationstester akzeptiert Argumente und gibt dann true_type zurück, wenn diese Argumente gültig wären, um andernfalls an f und false_type übergeben zu werden.

RETURNS macht es einfach, eine einzelne Aussage Lambda SFINAE freundlich zu machen. Und in C++ 17 sind die Lambda-Operationen, wenn möglich, constexpr (weshalb wir hier C++ 17 brauchen).

Dann das gibt uns:

template<class T> 
constexpr auto can_dot_size(T&& t) { 
    return can_invoke([](auto&& x) RETURNS(x.size()))(t); 
} 

Nun oft wir dies tun, weil wir .size() nennen, wenn möglich wollen, und das Rück sonst 0.

template<class T, class A, class...Bs> 
decltype(auto) call_first_valid(T&& t, A&& a, Bs&&...bs) { 
    if constexpr(can_invoke(std::forward<A>(a))(std::forward<T>(t))) { 
    return std::forward<A>(a)(std::forward<T>(t)); 
    else 
    return call_first_valid(std::forward<T>(t), std::forward<Bs>(bs)...); 
} 

jetzt können wir

Zufällig hat @Barry eine Funktion in C++ 20 vorgeschlagen, dieersetztmit [](auto&& f)=>f.size() (und mehr).

Verwandte Themen