parameter c++ c++11 variadic-templates c++14 typetraits

c++ - parameter - Compruebe los rasgos para todos los argumentos de la plantilla variadic



args c++ (2)

Definir all_true como

template <bool...> struct bool_pack; template <bool... v> using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;

Y reescribe tu constructor para

// Check convertibility to B&; also, use the fact that getA() is non-const template<typename... Args, typename = std::enable_if_t<all_true<std::is_convertible<Args&, B&>{}...>> C(Args&... args) : member{args.getA()...} {}

Alternativamente, bajo C ++ 17,

template<typename... Args, typename = std::enable_if_t<(std::is_convertible_v<Args&, B&> && ...)>> C(Args&... args) : member{args.getA()...} {}

Me temo que si uso un predicado como std :: is_base_of, obtendré una instanciación diferente del constructor para cada conjunto de parámetros, lo que podría aumentar el tamaño del código compilado ...

enable_if_t<…> siempre producirá el tipo void (con solo un argumento de plantilla dado), por lo que esto no puede ser culpa de is_base_of . Sin embargo, cuando Args tiene diferentes tipos, es decir, los tipos de los argumentos son distintos, a continuación se instanciarán diferentes especializaciones. Aunque esperaría un compilador para optimizar aquí.

Si desea que el constructor tome precisamente N argumentos, puede usar un método algo más fácil. Definir

template <std::size_t, typename T> using ignore_val = T;

Y ahora me especializo parcialmente como C

// Unused primary template template <size_t N, typename=std::make_index_sequence<N>> class C; // Partial specialization template <size_t N, std::size_t... indices> class C<N, std::index_sequence<indices...>> { /* … */ };

La definición del constructor dentro de la especialización parcial ahora se vuelve trivial.

C(ignore_val<indices, B&>... args) : member{args.getA()...} {}

Además, ya no tiene que preocuparse por una tonelada de especializaciones.

Antecedentes: he creado la siguiente clase C , cuyo constructor debería tomar N variables de tipo B& :

class A; class B { A* getA(); }; template<size_t N> class C { public: template<typename... Args> inline C(Args&... args) : member{args.getA()...} {} private: std::array<A*, N> member; };

Problema: mi problema es cómo restringir los Args Args para que sean todos de tipo B ?

Mi solución parcial: quería definir un predicado como:

template <typename T, size_t N, typename... Args> struct is_range_of : std::true_type // if Args is N copies of T std::false_type // otherwise {};

Y redefinir mi constructor en consecuencia:

template <typename... Args, typename = typename std::enable_if<is_range_of_<B, N, Args...>::value>::type > inline C(Args&... args);

He visto una posible solución en esta publicación: https://stackoverflow.com/a/11414631 , que define un predicado genérico check_all :

template <template<typename> class Trait, typename... Args> struct check_all : std::false_type {}; template <template<typename> class Trait> struct check_all<Trait> : std::true_type {}; template <template<typename> class Trait, typename T, typename... Args> struct check_all<Trait, T, Args...> : std::integral_constant<bool, Trait<T>::value && check_all<Trait, Args...>::value> {};

Entonces, podría escribir algo como:

template <typename T, size_t N, typename... Args> struct is_range_of : std::integral_constant<bool, sizeof...(Args) == N && check_all<Trait, Args...>::value > {};

Pregunta 1: No sé cómo definir el Trait , porque de alguna manera necesito unir std::is_same con B como primer argumento. ¿Hay algún medio para usar el check_all genérico en mi caso, o es la gramática actual de C ++ incompatible?

Pregunta 2: Mi constructor también debería aceptar las clases derivadas de B (a través de una referencia a B ), ¿es un problema para la deducción de argumentos de plantilla? Me temo que si uso un predicado como std::is_base_of , obtendré una instanciación diferente del constructor para cada conjunto de parámetros, lo que podría aumentar el tamaño del código compilado ...

Edición: Por ejemplo, tengo B1 y B2 que hereda de B , llamo C<2>(b1, b1) y C<2>(b1, b2) en mi código, creará dos instancias (de C<2>::C<B1, B1> y C<2>::C<B1, B2> )? Solo quiero instancias de C<2>::C<B, B> .


namespace detail { template <bool...> struct bool_pack; template <bool... v> using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>; template<class X> constexpr X implicit_cast(std::enable_if_t<true, X> x) {return x;} };

El bool_pack implicit_cast también está en Boost, el bool_pack robado de share .

// Only callable with static argument-types `B&`, uses SFINAE template<typename... ARGS, typename = std::enable_if_t< detail::all_true<std::is_same<B, ARGS>...>>> C(ARGS&... args) noexcept : member{args.getA()...} {}

Opción uno, si es implícitamente convertible eso es lo suficientemente bueno

template<typename... ARGS, typename = std::enable_if_t< detail::all_true<!std::is_same< decltype(detail::implicit_cast<B&>(std::declval<ARGS&>())), ARGS&>...>> C(ARGS&... args) noexcept(noexcept(implicit_cast<B&>(args)...)) : C(implicit_cast<B&>(args)...) {}

Opción dos, solo si se derivan públicamente de B y son inequívocamente convertibles:

// Otherwise, convert to base and delegate template<typename... ARGS, typename = decltype( detail::implicit_cast<B*>(std::declval<ARGS*>())..., void())> C(ARGS&... args) noexcept : C(implicit_cast<B&>(args)...) {}

El tipo de argumento-plantilla-ctor sin nombre es void en cualquier sustitución exitosa.