Question

I have a constructor like that :

class MyClass
{
    template<class TI> MyClass(TI first, TI last);
};

template<class TI> MyClass::MyClass(TI first, TI last)
{
    ;
}

I would like to enable this constructor only if TI is an iterator (that means TI has an iterator_category I think). How to do that in C++ 2011 using an enable_if as a default template parameter (in the declaration and in the definition) ?

Thank you very much.

Was it helpful?

Solution

It depends on what you want. If there are no other overloads, it can be ok with just nothing at all. The compiler will produce an error if a type is passed that doesn't provide the necessary operation.

If you really want to limit it to iterators, it's preferable to do so with a static_assert, since it produces an error with a nice custom error message, instead of "ambiguous function call, here are all the gazillion overloads I could find: follows endless list of overloads" or "could not find function, find it yourself".

If there is another templated overload that conflicts, then you do indeed need some enable_if thing. I wrote a blog post about using enable_if with C++11 features, and why default template parameters are not very good for that. I settled with something like this instead:

enum class enabler {};

template <typename Condition>
using EnableIf = typename std::enable_if<Condition::value, enabler>::type;


class MyClass
{
    template<class TI, EnableIf<is_iterator<TI>>...> MyClass(TI first, TI last);
};

template<class TI, EnableIf<is_iterator<TI>>...> MyClass::MyClass(TI first, TI last)
{ /* blah */ }

All that you need now is a trait for the test. I think testing for the existence of iterator_category is enough, but it should be done with std::iterator_traits, because pointers are iterators and don't have nested typedefs.

That can be done with the usual techniques that use SFINAE. With C++11, I do the following:

template <typename T>
struct sfinae_true : std::true_type {};

struct is_iterator_tester {
    template <typename T>
    static sfinae_true<typename std::iterator_traits<T>::iterator_category> test(int);

    template <typename>
    static std::false_type test(...);
};

template <typename T>
struct is_iterator : decltype(is_iterator_tester::test<T>(0)) {};

All that said, this could have been done with the traditional technique of using a defaulted function parameter:

class MyClass
{
    template<class TI>
    MyClass(TI first, TI last,
            typename std::iterator_traits<T>::iterator_category* = nullptr)
};

template<class TI>
MyClass::MyClass(TI first, TI last,
                 typename std::iterator_traits<T>::iterator_category*)
{ /* blah */ }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top