2016-09-27 34 views
11

To pytanie zostało zainspirowane przez this answer. Zastanawiam się, jakie są/były najlepsze sposoby na uproszczenie go w danych standardach. Jedno wiem i osobiście używane/nadal korzystać od C++ 14 jest makro REQUIRES(x):Jak uprościć skomplikowaną składnię SFINAE w wersjach przed C++ 11, C++ 11, 14 i 17?

Z definicji:

template<long N> 
struct requires_enum 
{ 
    enum class type 
    { 
     none, 
     all 
    }; 
}; 

#define REQUIRES(...) requires_enum<__LINE__>::type = \ 
         requires_enum<__LINE__>::type::none, \ 
         bool PrivateBool = true, \ 
         typename std::enable_if<PrivateBool && (__VA_ARGS__), int>::type = 0 

I użyć, jeśli nawet funkcja nie zwraca na matrycy:

template<REQUIRES(sizeof(int)==4)> 
int fun() {return 0;} 

int main() 
{ 
    fun(); //only if sizeof(int)==4 
} 

Oryginalny REQUIRES, którego używam, pochodzi z tego post.

Jakie są inne dobre techniki?


Niektóre przykłady SFINAE które wymagają trochę albo dużo czasu, aby zrozumieć, że dla czytelnika właśnie rozpoczął przygodę z SFINAE:

Pre-C++ 11 SFINAE przykładzie (Source):

template <typename T> 
struct has_typedef_foobar { 
    // Types "yes" and "no" are guaranteed to have different sizes, 
    // specifically sizeof(yes) == 1 and sizeof(no) == 2. 
    typedef char yes[1]; 
    typedef char no[2]; 

    template <typename C> 
    static yes& test(typename C::foobar*); 

    template <typename> 
    static no& test(...); 

    // If the "sizeof" of the result of calling test<T>(nullptr) is equal to sizeof(yes), 
    // the first overload worked and T has a nested type named foobar. 
    static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes); 
}; 
+2

To wygląda świetnie! – vsoftco

+1

Szczerze mówiąc, dlaczego nie wystarczy wpisać nazwę std :: enable_if = 0'? Ach, nie zależy to od parametru szablonu. Dlaczego "require_enum"? Podpis kolizji? – Yakk

+0

@Zobacz link do wpisu dodanego pod przykładem. – xinaiz

Odpowiedz

7

Jeśli pracujesz z C++ 11 (przykładowy kod zawiera std::enable_if, więc myślę, że jest to przypadek) lub kolejny przegląd, chciałbym użyć static_assert w tym przypadku:

int fun() { 
    static_assert(sizeof(int)==4, "!"); 
    return 0; 
} 

int main() { 
    fun(); 
} 

Nie masz zestaw funkcji, z którego można wybrać pracę jedną.
Jak już raz powiedziano, jest to bardziej nieudane zastąpienie zawsze jest błędem niż niepowodzenie zastępowania nie jest błędem.
Co chcesz, to wyzwalacz czasu kompilacji, a static_assert robi to z delikatnymi komunikatami o błędach.

Oczywiście jest to również łatwiejsze do odczytania niż skomplikowane wyrażenie sfinae!


Jeśli chcesz wybrać pomiędzy dwoma funkcjami i nie chcesz używać maszyn lub makr szablonu, nie zapomnij, że przeciążenie jest częścią języka (pre-C++ 11 przykład roboczy):

#include <iostream> 

template<bool> struct tag {}; 
int fun(tag<true>) { return 0; } 
int fun(tag<false>) { return 1; } 
int fun() { return fun(tag<sizeof(int) == 4>()); } 

int main() { 
    std::cout << fun() << std::endl; 
} 

ten można łatwo rozszerzyć na przypadki, w których funkcje są więcej niż dwa:

#include <iostream> 

template<int> struct tag {}; 
int fun(tag<0>) { return 0; } 
int fun(tag<1>) { return 1; } 
int fun(tag<2>) { return 2; } 

int fun(bool b) { 
    if(b) { return fun(tag<0>()); } 
    else { return fun(tag<(sizeof(int) == 4) ? 1 : 2>()); 
} 

int main() { 
    std::cout << fun(false) << std::endl; 
} 

można umieścić te funkcje w anonimowej przestrzeni nazw i uciec z nimi.


Oczywiście, uwaga, że ​​w pre-C++ 11 zostaliśmy upoważnieni do pisania enable_if i wszystkie inne rzeczy z type_traits dla siebie.
Jako przykład:

template<bool b, typename = void> 
struct enable_if { }; 

template<typename T> 
struct enable_if<true, T> { typedef T type; }; 
+0

To prawda, że ​​było to przesadą dla danego przykładu, ale było to tylko ze względu na przykład :) – xinaiz

+0

@BlackMoses Cóż, jeśli masz więcej niż jedną funkcję do wyboru, użyłbym 'enable_if' jako zwrotu rodzaj. Jest łatwy do zdefiniowania w Pre-C++ 11. – skypjack

+0

@BlackMoses Dodano więcej szczegółów do odpowiedzi. – skypjack

3

Włącz, jeśli jest dość łatwe do wdrożenia.Rzuć okiem na tę implementację:

template<bool b, typename T = void> 
struct enable_if { 
    typedef T type; 
}; 

template<typename T> 
struct enable_if<false, T> {}; 

W C++ 11 zwykle zadeklaruję kilka aliasów. Od utkniesz w ampułko C++ 11 ery, można to zrobić w zamian:

template<bool b> 
struct enable_if_parameter : enable_if<b, int*> {}; 

Następnie można użyć struct tak:

template<typename T, typename enable_if_parameter<(sizeof(T) >= 0)>::type = 0> 
void someFunc() { 
    // ... 
} 

Jeśli możesz pozwolić sobie niektóre C++ 17, można to zrobić:

template<bool b> 
using enable_if_parameter = std::enable_if_t<b, int*>; 

a następnie wykonaj że:

template<typename T, enable_if_parameter<std::is_same_v<T, int>> = 0> 

Uwielbiam też void_t Idom tworzyć nowe cechy typu:

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

template<typename T> 
struct has_callme<T, void_t<decltype(std::declval<T>().callme())>> : std::true_type {}; 
2

w C++ 03 po prostu napisać enable_if siebie. Nie wymaga funkcji C++ 11.

Powodem, dla którego używasz różnych technik jest to, że kompilatory sprzed C++ 11 czasami mają zabawną definicję SFINAE i co powinno być błędem. MSVC jest obecnie głównym kompilatorem, który wciąż (w erze przed C++ 17) ma bardzo dziwną definicję tego, co jest poprawne SFINAE ze względu na problemy z "SFINAE decltype".


w C++ 11 należy wpisać void_t i enable_if_t uprościć swoje rzeczy SFINAE.

Należy również napisać to:

namespace details { 
    template<template<class...>class Z, class always_void, class...Ts> 
    struct can_apply:std::false_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...>; 

który pozwala pisać cechy i zapytać, czy coś jest ważne łatwo (można wywołać metodę Tworzenie aliasu że robi decltype na pw, a następnie zapytać, czy możesz zastosować typ do aliasu). Jest to nadal potrzebne w C++ 14 i 17, ale C++ 20 prawdopodobnie dostanie is_detected, który służy podobnemu celowi.

Więc can_print jest:

template<class T>using print_result = decltype(
    std::declval<std::ostream&>() << std::declval<T>() 
); 
template<class T>using can_print = can_apply< print_result, T >; 

to albo truthy lub falsy zależności czy << prac nad strumieniem z nim.

W C++ 14 możesz zacząć korzystać z metaprogramowania w stylu hana, aby utworzyć lambdy, które wprowadzają manipulację. W C++ 17 stają się constexpr, co pozbywa się niektórych problemów.


Stosowanie takich technik, jak makro OP, prowadzi zwykle do źle sformułowanych programów, nie wymaga diagnostyki. Dzieje się tak dlatego, że jeśli szablon nie ma poprawnych parametrów szablonu, które prowadziłyby do tego, że treść szablonu jest poprawnym kodem, twój program jest źle sformułowany, nie wymaga diagnostyki.

Więc tak:

template<REQUIRES(sizeof(int)==4)> 
int fun() { 
    // code that is ill-formed if `int` does not have size 4 
} 

najprawdopodobniej skompilować i uruchomić i „rób co chcesz”, ale to jest rzeczywiście źle sformułowane podczas sizeof(int) Program jest 8.

To samo może się zdarzyć w przypadku użycia tej techniki do wyłączenia metod na klasach na podstawie argumentów szablonu klasy. Stanard jest niejasny w tej kwestii, więc go unikam.

Makro REQUIRES próbuje ukryć, jak działa za magią, ale zbyt łatwo jest przekroczyć linię i wygenerować źle sformułowany program. Ukrywanie magii, gdy szczegóły magii powodują, że twój kod jest źle sformułowany, nie jest dobrym planem.


Wysyłanie tagów może być wykorzystane do uproszczenia skomplikowanych problemów SFINAE. Może być używany do porządkowania przeciążeń lub wybierania między nimi lub przekazywania więcej niż jednego pakietu typów do funkcji szablonu pomocy.

template<std::size_t N> 
struct overload_priority : overload_priority<N-1> {}; 
template<> 
struct overload_priority<0> {}; 

Teraz można przejść overload_priority<50>{} do zestawu funkcji, a jedną z najwyższaoverload_priority<?> w tym gnieździe będą korzystne.

template<class T>struct tag_t{using type=T;}; 

namespace details { 
    inline int fun(tag_t<int[4]>) { return 0; } 
    inline int fun(tag_t<int[8]>) { return 1; } 
} 
int fun() { return details::fun(tag_t<int[sizeof(int)]>{}); } 

wysłane do innej funkcji w zależności od rozmiaru int.

Obie przeciążenia fun są kompilowane i sprawdzane, więc nie napotkasz na problem z ukrycia źle sformułowanego programu.

funkcją, której ważność jest nie funkcją jej argumentów szablonu jest nie bezpiecznie używać w C++. Musisz użyć innej taktyki. Maszyny, które ułatwiają to zadanie, ułatwiają pisanie źle sformułowanych programów.