Search code examples
c++templatesiteratortype-traitsstatic-assert

How can static_assert be used to check element type of iterator argument to templated function?


I have a template for a function that takes an output iterator argument. How can I use static_assert to check that an instantiation uses an appropriate iterator? (Ie, both that it is an output iterator, and that it assigns elements of the correct type.)

#include <iostream>
#include <list>
#include <set>

template <class OutputIter>
void add_ints ( OutputIter iter )
{
    static_assert ( something_goes_here,
                    "Arg must be an output iterator over ints" );

    *iter++ = 1;
    *iter++ = 2;
    *iter++ = 3;
}

main()
{
    // Insert iterator will add three elements.
    std::set<int> my_set;
    add_ints ( std::inserter ( my_set, my_set.end() ) );
    for ( int i : my_set ) std::cout << i << "\n";

    // Non-insert iterator will overwrite three elements.
    std::list<int> my_list ( { 0, 0, 0 } );
    add_ints ( my_list.begin() ) );
    for ( int i : my_list ) std::cout << i << "\n";

#if 0
    // Want nice compile error that container elements are not ints.
    std::set<std::string> bad_set;
    add_ints ( std::inserter ( bad_set, bad_set.end() ) );
#endif
#if 0
    // Want nice compile error that argument is not an iterator.
    class Foo {} foo;
    add_ints ( foo );
#endif
}

Solution

  • OutputIterators are not required to have value types; their value_type may well be void, and in fact is void for the purely output iterators from the standard library.

    In your original question you checked for output_iterator_tag, but you should not. There are plenty of perfectly mutable iterators that have a different category. std::vector<int>::iterator's category is random_access_iterator_tag, for instance.

    Instead, check the well-formed-ness of the applicable expressions directly. All Iterators must support *r and ++r, and in addition OutputIterators must support *r = o, r++, and *r++ = o, so:

    template<class...>
    struct make_void { using type = void; };
    
    template<class... Ts>
    using void_t = typename make_void<Ts...>::type;
    
    template<class Iter, class U, class = void>
    struct is_output_iterator_for : std::false_type {};
    
    template<class Iter, class U>
    struct is_output_iterator_for<
        Iter, U, 
        void_t<decltype(++std::declval<Iter&>()),
               decltype(*std::declval<Iter&>() = std::declval<U>()),
               decltype(*std::declval<Iter&>()++ = std::declval<U>())>> : std::true_type {};