简体   繁体   中英

Function specialization in template definition with template template parameters

Following the advise of @Jonas in a previous question , I am defining a templatized class to hold an arbitrary container (vector, set, map, etc) of an arbitrary containee (string, tree, etc). So far, my definition is like this:

template <template <typename...> class Container, typename Containee = std::string, typename... extras>
class Lemario
{
public:
    typedef typename Container<Containee, extras...>::iterator iterator;
    typedef typename Container<Containee, extras...>::const_iterator const_iterator;
    // Use 'Containee' here (if needed) like sizeof(Containee)
    // or have another member variable like: Containee& my_ref.
    Container<Containee, extras ...> mTheContainer;
    int loadContainees(const char *filename) {
        Containee w, line;
        // do some stuff here
    }
    void appendContainee(const Containee &__x);
};

Now, I can define methods both inline (like loadContainees) an outside of the template definition. Outside:

template <template <typename...> class Container, typename Containee, typename... extras>
Containee Lemario<Container, Containee, extras...>::transform_word(const     Containee& word) const
{
    Containee result;
    return result;
}

So far so good.

But now I want to specialize a method to append a Contaniee to the Container as vector, map, tree, use different methods. So I try to specialize std::vector:

template <template <typename...> class Container, typename Containee, typename... extras>
void Lemario<std::vector, Word>::appendContainee(const Word & word)
{
     mTheContainer.push_back(word);
}

But I get the following error:

error: prototype for ‘void Lemario<std::vector, gong::Xtring>::appendContainee(const Word&)’ does not match any in class ‘Lemario<std::vector, gong::Xtring>’

Apart from that, ¿can I only specialize the Container, std::vector, but let the Containee unspecialized?

template <template <typename...> class Container, typename Containee, typename... extras>
void Lemario<std::vector, Containee>::appendContainee(const Containee & word)
{
    mTheContainer.push_back(word);
}

You can specialize the function using std::enable_if (and SFINAE), here is an example, online .

template <template <typename...> class Container, typename Containee = std::string, typename... extras>
class Lemario
{
public:
    typedef typename Container<Containee, extras...>::iterator iterator;
    typedef typename Container<Containee, extras...>::const_iterator const_iterator;
    // Use 'Containee' here (if needed) like sizeof(Containee)
    // or have another member variable like: Containee& my_ref.
    Container<Containee, extras ...> mTheContainer;
    int loadContainees(const char *filename) {
        Containee w, line;
        // do some stuff here
        return 0; // To make the compiler happy
    }

    template <typename Containee2> // For SFINAE to work
    typename std::enable_if<std::is_same<Container<Containee2, extras...>, std::vector<Containee2, extras...>>::value>::type
    appendContainee(const Containee2 & word)
    {
         mTheContainer.push_back(word);
    }

    template <typename Containee2> // For SFINAE to work
    typename std::enable_if<std::is_same<Container<Containee2, extras...>, std::deque<Containee2, extras...>>::value>::type
    appendContainee(const Containee2 & word)
    {
         mTheContainer.push_back(word);
    }
};

Example

int main()
{
  Lemario<std::vector, int> foo;
  std::cout << foo.mTheContainer.size() << std::endl;
  foo.appendContainee(1);
  std::cout << foo.mTheContainer.size() << std::endl;

  Lemario<std::deque, int> bar;
  std::cout << bar.mTheContainer.size() << std::endl;
  bar.appendContainee(1);
  std::cout << bar.mTheContainer.size() << std::endl;
}

The problem is that your implementation of appendContainee has syntax errors. The proper way to specialize the function for std::vector and Word is to write it like so:

template <>
void Lemario<std::vector, Word>::appendContainee(const Word & word)
{
     mTheContainer.push_back(word);
}

Demo


This approach, however, requires that you FULLY specialize the function each time, meaning you have to specify both the container and the type. Likely you only want to specialize for std::vector and not also for Word .

This problem is classically solved with a tagged-dispatch pattern. In tagged dispatch, we turn specialization into an overloading problem by introducing helper methods that will be selected properly via overloading.

We create an empty nested templated struct within our class:

private:
   template<class...>
   struct Lemario_tag{};

And then write private helper methods that the entry point method will call:

template<class T>
void appendContaineeHelp(const Containee &x, Lemario_tag<T>)
{
    static_assert(sizeof(T) == 0, "No specialization exists for this container");
}

void appendContaineeHelp(const Containee &x, Lemario_tag<std::vector<Containee, extras...>>)
{
    mTheContainer.push_back(x);
}

The first is a catch-all that will cause a compiler error if you try to call it with a non-specialized container.

The second is specialized for a std::vector .

We define our public appendContainee like so (simple pass-through):

void appendContainee(const Containee &x){
    appendContaineeHelp(x, Lemario_tag<Container<Containee, extras...>>{});
}

We can use our container like so:

Lemario<std::vector, std::string> vecString;
Lemario<std::vector, int> vecInt;
vecString.appendContainee("foo");
vecInt.appendContainee(1);

Lemario<std::set, int> set_int;
// set_int.appendContainee(1); // compiler error

Better Demo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM