Question

I have a template action method that accepts any kind of STL container C. However the contained items (C::value_type) must be either ClassA or ClassB. So far so good:

struct Whatever {
    template<typename C>
    void action(const C& c) {
        static_assert(std::is_same<typename C::value_type, ClassA>::value ||
                      std::is_same<typename C::value_type, ClassB>::value,
                      "Wrong C::value_type");
        // do something with c
    }
};

// Usage:
Whatever w;
w.action(std::vector<ClassA>{1, 2, 3});
w.action(std::unordered_set<ClassB>{1, 2, 3});

Note: the enclosing class is not a template, the only template is this very action method.

Now, depending on C::value_type, I want to specialize the behaviour of the method. You guessed it, that's where my brain starts melting.


I believe SFINAE is the way to go but obviously I'm too rusty to make it work properly: after several hours, tons of helper structs and way too much coffee for my own health, the compiler just keeps yelling the usual 500+ template errors at me. No point in copying either my helper structs or the errors here, that's pretty much useless junk.

However, I have to admit I didn't really keep in touch with all the C++ template (r)evolutions (or even use SFINAE, at that) since a good decade so it's no wonder I'm failing so hard.

I strongly suspect C++11 now has easy, ready-to-use SFINAE-like tools to achieve what I want but I don't even know where to begin searching in the documentation. Search engines didn't help either, there's just too much new information at once for me to be able to understand what is/isn't relevant to my problem.

Since I'm at a total loss, I'll just take baby steps and ask SO... My question is two-fold:

  • How can I specialize the behaviour of the method depending on the actual type of C::value_type using modern C++11 template tools?
  • Optionally, is there a standard way to check if C actually is a container?

Thanks for your attention.

Was it helpful?

Solution

I'm wondering why don't you do this:

 template<typename C>
 void action(const C& c) 
 {
        static_assert(std::is_same<typename C::value_type, ClassA>::value ||
                      std::is_same<typename C::value_type, ClassB>::value,
                      "Wrong C::value_type");

      action_worker(c, static_cast<typename C::value_type*>(0));
 }

private:

 template<typename C>
 void action_worker(const C& c, ClassA *) 
 {
     //specialized code when C::value_type is ClassA
 }

 template<typename C>
 void action_worker(const C& c, ClassB *) 
 {
     //specialized code when C::value_type is ClassB
 }

Now depending on C::value_type, the second argument to action_worker can be either ClassA* or ClassB*. That will enable compiler to choose the correct overload where you write the specialized code.

For the second part of your question, see the implementation of is_container class template in this answer:

Hope that helps.

OTHER TIPS

As you only support those two types I would consider using enable_if:

struct Whatever
{
    template<typename C>
    typename std::enable_if<std::is_same<typename C::value_type, ClassA>::value, void>::type
    action(const C& c)
    {
        std::cout << "ClassA\n";
    }

    template<typename C>
    typename std::enable_if<std::is_same<typename C::value_type, ClassB>::value, void>::type
    action(const C& c)
    {
        std::cout << "ClassB\n";
    }
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top