18

Background : I've created the following class C, whose constructor should take N variables of type 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;
};

Problem : my problem is how to constraint the variadic Args to be all of type B ?

My partial solution : I wanted to define a predicate like :

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
{};

And redefine my constructor accordingly :

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

I've seen a possible solution on this post : https://stackoverflow.com/a/11414631, which defines a generic check_all predicate :

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>
{};

So, I could write something like :

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

Question 1 : I don't know how to define the Trait, because I need somehow to bind std::is_same with B as first argument. Is there any means of using the generic check_all in my case, or is the current grammar of C++ incompatible ?

Question 2 : My constructor should also accept derived classes of B (through a reference to B), is it a problem for template argument deduction ? I am afraid that if I use a predicate like std::is_base_of, I will get a different instantiation of the constructor for each set of parameters, which could increase compiled code size...

Edit : For example, I have B1 and B2 that inherits from B, I call C<2>(b1, b1) and C<2>(b1, b2) in my code, will it create two instances (of C<2>::C<B1, B1> and C<2>::C<B1, B2>) ? I want only instances of C<2>::C<B, B>.

Community
  • 1
  • 1
Steakfly
  • 357
  • 3
  • 11
  • Do you want them to be derived from `B`, or just implicitly convertible to `B`? – Deduplicator Jan 31 '15 at 16:02
  • I want them to be derived from `B`, see my edit. I need a template generalization to `N` arguments of a class that defines the constructor `C(B& b)`. – Steakfly Jan 31 '15 at 16:11
  • The arguments being derived from `B` is a both a stronger and a weaker constraint than being convertible. As neither implies the other. – Deduplicator Jan 31 '15 at 16:17
  • They'd better hurry up with those [concepts stuff](http://isocpp.org/blog/2013/02/concepts-lite-constraining-templates-with-predicates-andrew-sutton-bjarne-s) =) – Ivan Aksamentov - Drop Jan 31 '15 at 17:40

2 Answers2

39

Define all_true as

template <bool...> struct bool_pack;

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

And rewrite your constructor to

// 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()...}
{}

Alternatively, under C++17,

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

I am afraid that if I use a predicate like std::is_base_of, I will get a different instantiation of the constructor for each set of parameters, which could increase compiled code size...

enable_if_t<…> will always yield the type void (with only one template argument given), so this cannot be is_base_ofs fault. However, when Args has different types, i.e. the types of the arguments are distinct, then subsequently different specializations will be instantiated. I would expect a compiler to optimize here though.


If you want the constructor to take precisely N arguments, you can use a somewhat easier method. Define

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

And now partially specialize C as

// 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...>>
{ /* … */ };

The definition of the constructor inside the partial specialization now becomes trivial

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

Also, you do not have to worry about a ton of specializations anymore.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 2
    Heh, `all_true` is definitely nicer than recursion. Not sure about using `is_base_of`, though, since that accepts inaccessible or ambiguous bases as well. – T.C. Jan 31 '15 at 16:07
  • @T.C. I think this isn't the optimal approach anyway. But how can is_base_of yield true for inaccessible bases? I thought it checks whether the derived-to-base conversion is applicable? – Columbo Jan 31 '15 at 16:09
  • 2
    Nice trick for the `all_true` and thanks for the C++14 syntax ! – Steakfly Jan 31 '15 at 16:12
  • Why do you decay the types? – Deduplicator Jan 31 '15 at 16:15
  • @Deduplicator I was thinking of forwarding references and cv-qualifiers. – Columbo Jan 31 '15 at 16:17
  • @Columbo There's a nice trick that allows you to do it in code: http://stackoverflow.com/questions/2910979/how-does-is-base-of-work; but an intrinsic works too. Up to the implementation, I guess. – T.C. Jan 31 '15 at 16:18
  • 1
    @Steakfly I did add another method that might be more suitable for you. – Columbo Jan 31 '15 at 16:23
  • @Columbo Thanks for the `index_sequence` trick. Will these indices introduce any tradeoff in the compiled code ? Otherwise this is exactly what I need ! – Steakfly Jan 31 '15 at 16:26
  • @Steakfly `index_sequence` is evaluated at compile time, so I believe that shouldn't affect the produced code in performance. – Columbo Jan 31 '15 at 16:32
  • @Columbo Using `is_convertible` on references permits user-defined conversions. Use it on pointers. – T.C. Jan 17 '16 at 20:32
  • @T.C. I did originally, before editing. However, the semantics of the constructor shouldn't necessarily be excluding user-defined conversions, should they? Compare with the second method? – Columbo Jan 17 '16 at 20:39
  • @Columbo Well, the question is framed as B and classes derived from it. Whether it's desired to allow user-defined conversions probably depends on the use case. – T.C. Jan 18 '16 at 02:21
  • The fold expression of the C++17 version requires parentheses: `template &&...)>>`. I cannot edit only two characters. – metalfox Sep 26 '17 at 15:08
1
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;}
};

The implicit_cast is also in Boost, the bool_pack stolen from Columbo.

// 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()...} {}

Option one, if it's implicitly convertible that's good enough

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)...) {}

Option two, only if they are publicly derived from B and unambiguously convertible:

// 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)...) {}

The unnamed ctor-template-argument-type is void in any successful substitution.

Community
  • 1
  • 1
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • So why are *you* decaying `Args`? – Columbo Jan 31 '15 at 17:26
  • By the way, why not just use `enable_if_t{}>`? – Columbo Jan 31 '15 at 17:35
  • 1
    The `remove_reference_t` is superfluous AFAICS. `Args` can never be deduced to a reference type, that is only possible for forwarding references. – Columbo Jan 31 '15 at 17:40
  • @Deduplicator : this implementation will generate several instantiations when used with different types, right ? (though they all delegate to the first constructor) – Steakfly Jan 31 '15 at 18:21
  • @Steakfly: Those many instantiations of the second template (whichever second one you might choose) are unavoidable. But they are all utterly trivial forwarders, and if they aren't outright collapsed with the forwarded-to constructor, they will certainly be inlined and eliminated without trace anyway. Also added `noexcept`-specifier. – Deduplicator Jan 31 '15 at 21:35