2016-01-06 3 views
7

У меня есть тип, как это:Условное определение типа псевдоним

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

Я хотел foo, bar, baz и эквивалентные псевдонимами типа, чтобы определить, если и только если эквивалентный тип в T существует. Решения с использованием std::conditional позволяют заменить его чем-то другим, когда он не существует, но я не знаю, как убедиться, что он вообще не существует, если соответствующий тип не существует в типе шаблона. Приведенный выше код вызывает ошибку при создании wrapper<T>, если T не определяет один из псевдонимов типа.

Я не могу сделать wrapper наследовать от T потому wrapper не должен делать все, что T может сделать. Кроме того, использование частичной специализации приведет к некоторому экспоненциальному взрыву и быстро станет недостижимым. Я мог бы сделать foo, bar ... шаблон псевдонимами типа впрыснуть в std::enable_if в параметре шаблона по умолчанию, но тогда пользователи должны написать wrapper<T>::foo<>, wrapper<T>::bar<> вместо wrapper<T>::foo, wrapper<T>::bar и т.д ... и я не хочу этого.

Есть ли простой, но поддерживаемый способ определения такого псевдонима типа, только если соответствующий псевдоним типа существует в T?

ответ

8

Вы можете определить check_foo, check_bar и check_baz черты, которые только имеют тип, если он существует, то наследовать от них всех в 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> 
{ }; 

Это одна дополнительная структура для каждого типа, но, безусловно, предпочтительнее, чем экспоненциальную версию, о которой вы говорили. Можно даже сделать это макрос, если вы были соответствующим образом порочных:

#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) 

Ужасное, я знаю, но я думаю, что вам, возможно, придется заплатить эту цену, если вы действительно хотите wrapper<T>::bar, а не wrapper<T>::bar<>. Если вы используете версию макроса, добавление нового типа означает только новый DEFINE_CHECKER(newname) и добавление check_newname<T> в список наследования обертки. Могло быть и хуже.

Live Demo

+1

Это решение, которое я имел в виду, и я был готов попробовать его. Это не очень красиво, но еще более удобно, чем другие решения, не подвергая пользователей бесполезным вещам. Спасибо :) – Morwenn

+0

Dang. Я надеялся, что это будет более приятное решение. – Justin

5

Обратите внимание, что ответ с помощью void_t по @TartanLlama прекрасно, как она есть. Однако в C++ 17, скорее всего, будет несколько помощников стандартной библиотеки, таких как is_detected_v, которые будут делать звонки void_t под капотом.

#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 Обратите внимание, что его это один из очень редких примеров, когда libstdC++ 6.0 Ствол SVN может (в настоящее время!) Сделать что-то, что LibC++ 3.9 Ствол SVN не может.

Для этого требуется добавить псевдоним детектора и структуру держателя для каждого вводимого типа и полностью исключить необходимость в макрооблоке.

+0

Действительно элегантное решение!Конечно, это можно сделать на C++ 14, используя соответствующие помощники или нагло крадя их, например [this] (http://coliru.stacked-crooked.com/a/cee230048c1a807c). – TartanLlama

+0

О, теперь это интересное решение: o – Morwenn

+0

@TemplateRex Хотя это не то, о чем просил @Morwenn, не так ли? 'wrapper ' просто пытается наследовать от 'int' три раза, а не наследовать псевдонимы' foo', 'bar' и' baz'. – TartanLlama

Смежные вопросы