HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Variadic function with restricted types

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withfunctionvariadictypesrestricted

Problem

I am designing a class which (among other things) acts as a vertex in an undirected graph.

#include 
#include 

class Node
{
public:
    explicit Node(const std::string& name);
    Node(const Node&);
    Node(Node&&);

    class iterator
        : public std::iterator
    {
        //...
    };
    class container_proxy
    {
    public:
        iterator begin() const;
        iterator end() const;
    private:
        //...
    };

    container_proxy neighbors() const;

    add_link(Node& other);

    //...

private:
    //...
};
// to support: for (const Node& neighbor : node.neighbors())
Node::iterator begin(const Node::container_proxy&);
Node::iterator end(const Node::container_proxy&);


Now I want to define a function link_complete which:

  • takes any number of arguments



  • allows non-const Node lvalues (parameter type Node&)



  • allows non-const Node rvalues (parameter type Node&&)



  • does not allow any other parameter type.



An example usage:

Node ret_by_value();

void example_test(Node& x, Node&& y)
{
    link_complete(x, ret_by_value(), std::move(y), Node("test"));
}


Here's the solution I came up with:

#include 
#include 

class Node {
    //...
public:
    static void link_complete(std::initializer_list node_list);
};

template 
struct template_all; // UNDEFINED

template <>
struct template_all<>
  : public std::true_type {};

template 
struct template_all
  : public std::false_type {};

template 
struct template_all
  : public template_all::type {};

template 
auto link_complete(Args&& ... args) ->
  typename std::enable_if::type,
        Node
      >::type ...
    >::value,
    void
  >::type
{
    Node::link_complete( { &args... } );
}


It seems to work as required, but I'm wondering if there was a simpler or "prettier" way to do it, or maybe a way to improve that template_all helper type. All C++11, TR1, and Boost features are welcome. I did notice boost::mpl::and_ looks similar to my template_all helpe

Solution

struct has public inheritance

By default, struct has public inheritance. Therefore, you don't need to manually specify it every time. You can write shorter (and so more readable) code:

template <>
struct template_all<>
  : std::true_type {};

template 
struct template_all
  : std::false_type {};

template 
struct template_all
  : template_all::type {};


Since type traits generally use many templates, you don't want to add even more extra keywords all over the place.

Use alias templates

If you have acces to a compiler that supports some C++14 features, you will probably want to use the alias templates for transformation traits to get rid of many typename and ::type. Unfortunately, the alias templates for the query traits (is_*) have not been accepted, so you have to use std::is_same.

template 
auto link_complete(Args&& ... args) ->
  std::enable_if_t,
        Node
      >...
    >::value,
    void
  >
{
    Node::link_complete( { &args... } );
}


static_assert is great

Instead of using SFINAE for link_complete, you should use static_assert since your function does not have any overload. It will allow you to give a meaningful error message instead of a somewhat obscure SFINAE error message:

template 
void link_complete(Args&& ... args)
{

    static_assert(
        template_all,
                Node
            >::type...
        >::value,
        "link_complete arguments must be of the type Node"
    );

    Node::link_complete( { &args... } );
}


You may want to use std::decay

std::decay calls std::remove_reference and std::remove_cv internally. If needed, it also calls std::remove_extent if T is an array type and calls std::add_pointer if T is a function type. In short:


This is the type conversion applied to all function arguments when passed by value.

That allows you to check for what we generally think of as "same types":

template 
void link_complete(Args&& ... args)
{

    static_assert(
        template_all,
                Node
            >::type...
        >::value,
        "link_complete arguments must be of the type Node"
    );

    Node::link_complete( { &args... } );
}


Rethink template_all

All in all, your goal is to test boolean conditions on types. Since the query traits have a static constexpr bool value member, you may want to create a template_all that checks for true and false (like std::enable_if or std::conditional) instead of checking for std::true_type and std::false_type. That would make your intent clearer, but it would be more like a all_true template.

template 
struct all_true; // UNDEFINED

template <>
struct all_true<>
  : std::true_type {};

template 
struct all_true
  : std::false_type {};

template 
struct all_true
  : all_true {};

template 
void link_complete(Args&& ...)
{
    static_assert(
        all_true,
                Node
            >::value...
        >::value,
        "must have Node types"
    );

    Node::link_complete( { &args... } );
}

Code Snippets

template <>
struct template_all<>
  : std::true_type {};

template <typename ... Types>
struct template_all<std::false_type, Types...>
  : std::false_type {};

template <typename ... Types>
struct template_all<std::true_type, Types...>
  : template_all<Types...>::type {};
template <typename ... Args>
auto link_complete(Args&& ... args) ->
  std::enable_if_t<
    template_all<
      typename std::is_same<
        std::remove_reference_t<Args>,
        Node
      >...
    >::value,
    void
  >
{
    Node::link_complete( { &args... } );
}
template <typename ... Args>
void link_complete(Args&& ... args)
{

    static_assert(
        template_all<
            typename std::is_same<
                std::remove_reference_t<Args>,
                Node
            >::type...
        >::value,
        "link_complete arguments must be of the type Node"
    );

    Node::link_complete( { &args... } );
}
template <typename ... Args>
void link_complete(Args&& ... args)
{

    static_assert(
        template_all<
            typename std::is_same<
                std::decay_t<Args>,
                Node
            >::type...
        >::value,
        "link_complete arguments must be of the type Node"
    );

    Node::link_complete( { &args... } );
}
template <bool...>
struct all_true; // UNDEFINED

template <>
struct all_true<>
  : std::true_type {};

template <bool... Conds>
struct all_true<false, Conds...>
  : std::false_type {};

template <bool... Conds>
struct all_true<true, Conds...>
  : all_true<Conds...> {};

template <typename ... Args>
void link_complete(Args&& ...)
{
    static_assert(
        all_true<
            std::is_same<
                std::decay_t<Args>,
                Node
            >::value...
        >::value,
        "must have Node types"
    );

    Node::link_complete( { &args... } );
}

Context

StackExchange Code Review Q#9736, answer score: 6

Revisions (0)

No revisions yet.