Search code examples
c++c++11hpx

Un-named function parameters


I'm looking at some code pertaining to the n3960 standard proposal and noticed some functions have parameters with no name, yet have a full function definition. Could someone explain how this is?

Example:

template <typename ExPolicy, typename IteratorTag>
void test_for_each(ExPolicy const& policy, IteratorTag) //just IteratorTag, no name?
{
    BOOST_STATIC_ASSERT(hpx::parallel::is_execution_policy<ExPolicy>::value);

    typedef std::vector<std::size_t>::iterator base_iterator;
    typedef test::test_iterator<base_iterator, IteratorTag> iterator;

    std::vector<std::size_t> c(10000);
    std::iota(boost::begin(c), boost::end(c), std::rand());

    hpx::parallel::for_each(policy,
        iterator(boost::begin(c)), iterator(boost::end(c)),
        [](std::size_t& v) {
            v = 42;
        });

    // verify values
    std::size_t count = 0;
    std::for_each(boost::begin(c), boost::end(c),
        [](std::size_t v) {
            HPX_TEST_EQ(v, std::size_t(42));
            ++count;
        });
    HPX_TEST_EQ(count, c.size());
}

Solution

  • As noted, argument names are optional. But why was this omitted, and why have an argument without a name?

    The technique being used here is function template tag type deduction based off of argument types.

    You can call test_for_each passing in an instance of an arbitrary type as the 2nd parameter. Whatever the value type for that argument ends up being passed to the template function as IteratorTag.

    Within the class, the value of the IteratorTag variable is not used -- all we care about is the type.

    IteratorTags are used to distinguish between the various kinds of std library iterators -- forward, random access, input, output, etc.

    With that type in hand, we can make subtle changes to how our code behaves (or less subtle, using overloading). In this case, the iterator type within the function takes the IteratorTag type as one of its template arguments, so the type of that variable differs based on the IteratorTag passed in.

    Here is a simple version of this using the tag for a technique called "tag dispatching":

    template<typename T>
    int floor_to_int( T&& t, std::true_type ) { return std::forward<T>(t); }
    template<typename T>
    int floor_to_int( T&& t, std::false_type ) { return floor(std::forward<T>(t)); }
    template<typename T>
    int smart_floor( T&& t ) {
      return floor_to_int( std::forward<T>(t), std::is_integral< typename std::decay<T>::type >{} );
    }
    

    here the smart_floor function takes an arbitrary type T, and if and only if it is a non-integral type calls floor on it. Otherwise, it just converts it to an int.

    We create an instance of the tag type -- in this case, std::is_integral< typename std::decay<T>::type > -- and pass it to an internal helper function. In this case, we have two overloads, neither of which uses the value of the passed in tag, just its type.

    The above implementation is similar, except it has 1 overload for both cases. Maybe that tag type will be used deeper in in a similar overloading way, or maybe it is specialized in some traits class at some deeper level of use.

    Odds are the argument here is supposed to be std::iterator_traits< T >::iterator_category or some homebrew boost variant, as an aside.