2016-01-06 25 views
7

Ho un tipo simile:condizionale definizione di tipo alias

template<typename T> 
struct wrapper 
{ 
    using foo = typename T::foo; 
    using bar = typename T::bar; 
    using baz = typename T::baz; 
    // More of those... 
}; 

desidero foo, bar, baz ed equivalenti di tipo alias da definire se e solo se il tipo equivalente esiste in T. Le soluzioni che utilizzano std::conditional consentono di sostituirlo con qualcos'altro quando non esiste, ma non so come assicurarsi che non esista affatto quando il tipo corrispondente non esiste nel tipo di modello. Il codice sopra riportato genera un errore quando wrapper<T> viene istanziato se T non definisce uno degli alias di tipo.

non posso fare wrapper erediti da T perché wrapper non dovrebbe fare tutto T può fare. Inoltre, l'utilizzo di una specializzazione parziale porterebbe a una sorta di esplosione esponenziale e diventerebbe rapidamente non mantenibile. Probabilmente potrei fare foo, bar ... tipo alias template per iniettare un std::enable_if in un parametro modello predefinito, ma poi gli utenti avrebbero dovuto scrivere wrapper<T>::foo<>, wrapper<T>::bar<> invece di wrapper<T>::foo, wrapper<T>::bar, ecc ... e io non voglio questo.

Esiste un modo semplice ma gestibile per definire un alias di questo tipo solo quando esiste l'alias di tipo corrispondente in T?

risposta

8

Si potrebbe definire check_foo, check_bar e check_baz tratti che hanno solo il tipo se esiste, quindi eredita da tutti loro in wrapper:

template <typename T, typename=void> 
struct check_foo{}; 

template <typename T> 
struct check_foo<T, void_t<typename T::foo>> { 
    using foo = typename T::foo; 
}; 

// ditto for bar, baz, etc. 

template <typename T> 
struct wrapper : 
    check_foo<T>, 
    check_bar<T>, 
    check_baz<T> 
{ }; 

E 'una struct in più per tipo, ma certamente preferibile alla versione esponenziale che hai menzionato. Si potrebbe anche fare una macro se si fosse adeguatamente perversa:

#define DEFINE_CHECKER(NAME) \ 
    template <typename T, typename=void> struct check_##NAME{}; \ 
    template <typename T> struct check_##NAME<T,void_t<typename T::NAME>> \ 
    { using NAME = typename T::NAME; }; 

DEFINE_CHECKER(foo) 
DEFINE_CHECKER(bar) 
DEFINE_CHECKER(baz) 

orribile, lo so, ma penso che potrebbe essere necessario pagare quel prezzo se si vuole veramente wrapper<T>::bar piuttosto che wrapper<T>::bar<>. Se si utilizza la versione macro, aggiungere un nuovo tipo significherebbe solo un nuovo DEFINE_CHECKER(newname) e aggiungere check_newname<T> all'elenco di ereditarietà del wrapper. Potrebbe essere peggio.

Live Demo

+1

Questa è la soluzione che avevo in mente ed ero pronto a provarlo. Non è molto carino, ma è più gestibile rispetto alle altre soluzioni senza esporre gli utenti a cose inutili. Grazie :) – Morwenn

+0

Dang. Speravo che ci sarebbe stata una soluzione migliore di questa. – Justin

5

Nota che la risposta utilizzando void_t da @TartanLlama va bene così com'è. Tuttavia, in C++ 17 probabilmente ci saranno un paio di helper per la libreria standard come is_detected_v che eseguiranno le chiamate a void_t sotto il cofano.

#include <experimental/type_traits> 

// helpers to reduce boilerplate 
template<class Tag> 
struct empty_base {}; 

template<template<class> class Holder, template<class> class Op, class Arg> 
using inject_or_t = std::conditional_t 
< 
    std::experimental::is_detected_v<Op, Arg>, 
    Holder<Arg>, 
    empty_base<Op<Arg>> 
>; 

// add detector + holder for every conditional nested type 

template<class T> 
using foo_t = typename T::foo; 

template<class T> 
struct foo_holder { using foo = foo_t<T>; }; 

template<class T> 
using bar_t = typename T::bar; 

template<class T> 
struct bar_holder { using bar = bar_t<T>; }; 

template<class T> 
using baz_t = typename T::baz; 

template<class T> 
struct baz_holder { using baz = baz_t<T>; }; 

// wrapper is now simply: 

template<class T> 
struct wrapper 
: inject_or_t<foo_holder, foo_t, T> 
, inject_or_t<bar_holder, bar_t, T> 
, inject_or_t<baz_holder, baz_t, T> 
{}; 

struct Test 
{ 
    using foo = int; 
    using bar = int; 
    using baz = int; 
}; 

int main() 
{ 
    static_assert(!std::experimental::is_detected_v<foo_t, wrapper<int>>); 
    static_assert(!std::experimental::is_detected_v<bar_t, wrapper<int>>); 
    static_assert(!std::experimental::is_detected_v<baz_t, wrapper<int>>); 

    static_assert(std::experimental::is_detected_v<foo_t, wrapper<Test>>); 
    static_assert(std::experimental::is_detected_v<bar_t, wrapper<Test>>); 
    static_assert(std::experimental::is_detected_v<baz_t, wrapper<Test>>); 
} 

Live Example Nota che il suo è uno dei rarissimi esempi in cui libstdC++ 6.0 SVN tronco può (attualmente!) Fare qualcosa che libC++ 3.9 tronco SVN non può.

Ciò richiede l'aggiunta di un alias del rilevatore e una struttura di supporto per ciascun tipo da iniettare e elimina completamente la necessità di un wrapper di macro.

+0

Soluzione davvero elegante!Naturalmente si potrebbe fare ciò in C++ 14 implementando gli helper appropriati, o rubandoli palesemente, come [questo] (http://coliru.stacked-crooked.com/a/cee230048c1a807c). – TartanLlama

+0

Oh, ora questa è una soluzione interessante: o – Morwenn

+0

@TemplateRex Anche se questo non è quello che @Morwenn ha chiesto, giusto? 'wrapper ' prova ad ereditare da 'int' tre volte piuttosto che ereditare alias di' foo', 'bar' e' baz'. – TartanLlama

Problemi correlati