12

I am currently doing some template metaprogramming. In my case I can handle any "iteratable" type, i.e. any type for which a typedef foo const_iterator exists in the same manner. I was trying to use the new C++11 template metaprogramming for this, however I could not find a method to detect if a certain type is missing.

Because I also need to turn on/off other template specializations based on other characteristics, I am currently using a template with two parameters, and the second one gets produced via std::enable_if. Here is what I am currently doing:

template <typename T, typename Enable = void>
struct Foo{}; // default case is invalid

template <typename T>
struct Foo< T, typename std::enable_if<std::is_fundamental<T>::value>::type>{ 
   void do_stuff(){ ... }
};

template<typename T>
struct exists{
   static const bool value = true;
};

template<typename T>
struct Foo<T, typename std::enable_if<exists< typename T::const_iterator >::value >::type> {
    void do_stuff(){ ... }
};

I was not able to do something like this without the exists helper template. For example simply doing

template<typename T>
struct Foo<T, typename T::const_iterator> {
    void do_stuff(){ ... }
};

did not work, because in those cases where this specialization should be used, the invalid default case was instantiated instead.

However I could not find this exists anywhere in the new C++11 standard, which as far as I know simply is taking from boost::type_traits for this kind of stuff. However on the homepage for boost::type_traits does not show any reference to anything that could be used instead.

Is this functionality missing, or did I overlook some other obvious way to achieve the desired behavior?

LiKao
  • 10,408
  • 6
  • 53
  • 91

4 Answers4

15

If you simply want if a given type contains const_iterator then following is a simplified version of your code:

template<typename T>
struct void_ { typedef void type; };

template<typename T, typename = void>
struct Foo {};

template<typename T>
struct Foo <T, typename void_<typename T::const_iterator>::type> {
      void do_stuff(){ ... }
};

See this answer for some explanation of how this technique works.

cxw
  • 16,685
  • 2
  • 45
  • 81
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • 3
    You should maybe post a link to your questions on how this one works. :) Also, it seems you've taken a liking to this one, seen you suggesting it multiple times already. – Xeo Oct 20 '11 at 11:51
  • @Xeo, yes this pretty easy and straight forward. But I din't get your first part `You should maybe post a link to your questions on how this one works.` :) ... do you mean while answering shall I put a link to my previous questions (instead of the code itself) ? I suspect that is not recommended on SO. – iammilind Oct 20 '11 at 11:55
  • 1
    Nono, what I meant was, that you post a link to [your question where you asked how this works](http://stackoverflow.com/questions/6543652/different-template-syntax-for-finding-if-argument-is-a-class-or-not), since it's not really obvious at first. Woops, and I just noted I wrote 'questions', only meant that one question of course. – Xeo Oct 20 '11 at 11:58
  • @Xeo, actually you were right for `questions`. Because my recent questions were somewhat related to this mechanism only. So, now I have got a good hold of it. – iammilind Oct 20 '11 at 12:07
  • why should we have the "struct void_" here? why we cannot simply pass the typename T::const_iterator in the specification? – xiaodong Jul 20 '16 at 09:47
  • 1
    @xiaodong, Basically when you have SFINAE, the yielding type should be same. For example, in this case the 'deciding type' is `void` which is used as a 2nd default type for default `struct Foo`. Now all the subsequent specializations of `Foo` must have the same 2nd type. For that very purpose we need `void_` mechanism. Also refer the link put by Xeo above. I had asked the same question as yours: [Different template syntax for finding if argument is a class or not](http://stackoverflow.com/questions/6543652/different-template-syntax-for-finding-if-argument-is-a-class-or-not). – iammilind Jul 20 '16 at 09:53
7

You can create a trait has_const_iterator that provides a boolean value and use that in the specialization.

Something like this might do it:

template <typename T>
struct has_const_iterator {
private:
    template <typename T1>
    static typename T1::const_iterator test(int);
    template <typename>
    static void test(...);
public:
    enum { value = !std::is_void<decltype(test<T>(0))>::value };
};

And then you can specialize like this:

template <typename T,
          bool IsFundamental = std::is_fundamental<T>::value,
          bool HasConstIterator = has_const_iterator<T>::value>
struct Foo; // default case is invalid, so no definition!

template <typename T>
struct Foo< T, true, false>{ 
   void do_stuff(){// bla }
};

template<typename T>
struct Foo<T, false, true> {
    void do_stuff(){//bla}
};
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 1
    Is there a reason you chose `test(int)` instead of, e.g., `test()`?Just making sure I understand how the overload resolution works here. Thanks! – nknight Aug 06 '12 at 04:58
  • 2
    @nknight: Reason: to make the call to `test` unambiguous. – erenon Sep 24 '13 at 09:35
4

Here's another version of a member type trait check:

template<typename T>
struct has_const_iterator
{
private:
    typedef char                      yes;
    typedef struct { char array[2]; } no;

    template<typename C> static yes test(typename C::const_iterator*);
    template<typename C> static no  test(...);
public:
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
2

There is a couple of ways to do this. In C++03, you could use boost and enable_if to define the trait (docs, source):

BOOST_MPL_HAS_XXX_TRAIT_DEF(const_iterator);

template <typename T, typename Enable = void>
struct Foo;

template <typename T>
struct Foo< T, typename boost::enable_if<boost::is_fundamental<T> >::type>{ 
   void do_stuff(){ ... }
};

template<typename T>
struct Foo<T, typename boost::enable_if<has_const_iterator<T> >::type> {
    void do_stuff(){ ... }
};

In C++11, you could use Tick like this:

TICK_TRAIT(has_const_iterator)
{
    template<class T>
    auto require(const T&) -> valid<
        has_type<typename T::const_iterator>
    >;
};

template <typename T, typename Enable = void>
struct Foo;

template <typename T>
struct Foo< T, TICK_CLASS_REQUIRES(std::is_fundamental<T>::value)>{ 
   void do_stuff(){ ... }
};

template<typename T>
struct Foo<T, TICK_CLASS_REQUIRES(has_const_iterator<T>())> {
    void do_stuff(){ ... }
};

Also with Tick you can further enhance the trait to actually detect that the const_iterator is actually an iterator, as well. So say we define a simple is_iterator trait like this:

TICK_TRAIT(is_iterator,
    std::is_copy_constructible<_>)
{
    template<class I>
    auto require(I&& i) -> valid<
        decltype(*i),
        decltype(++i)
    >;
};

We can then define has_const_iterator trait to check that the const_iterator type matches the is_iterator trait like this:

TICK_TRAIT(has_const_iterator)
{
    template<class T>
    auto require(const T&) -> valid<
        has_type<typename T::const_iterator, is_iterator<_>>
    >;
};
cxw
  • 16,685
  • 2
  • 45
  • 81
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59