4
\$\begingroup\$

I'm trying to improve my C++ template metaprogramming skills. From my understanding, I can't partially specialize a function(either member function or not). So, I need to define another class that can be used as template class, which enables us partially specialize template argument.

So, In order to check my understanding, I've wrote a simple code. It uses general template class and partially specialized template class to deal with various types. (I've tested this code using gcc 4.8 / clang 3.4 / MSVC 2015) It would give a result as follows:

Generic

Specialization for std::string

Specialization for bool

Specialization for integral type

Am I doing write using class templates? I need a review for this. Any comment would be helpful.

// a.hpp
#ifndef A_HPP
#define A_HPP
#include <iostream>
#include <string>
#include <type_traits>

// declaration & implementation for bar_impl::bar
template <typename T, typename T1, typename T2, typename T3>
struct bar_impl
{
    static void bar(const T& input)
     {
        (void)input;
        std::cout << "Generic" << std::endl;
    }
};

template <typename T>
struct bar_impl<T, std::false_type, std::true_type, std::false_type>
{
    static void bar(const T& input)
    {
        (void)input;
        std::cout << "Specialization for integral type" << std::endl;
    }
};

template <typename T>
struct bar_impl<T, std::false_type, std::true_type, std::true_type>
{
    static void bar(const T& input)
    {
        (void)input;
        std::cout << "Specialization for bool" << std::endl;
    }
};

template <typename T, typename T1, typename T2>
struct bar_impl<T, std::true_type, T1, T2>
{
    static void bar(const T& input)
    {
        (void)input;
        std::cout << "Specialization for std::string" << std::endl;
    }
};

struct Foo
{
    template <typename T>
    void bar(const T& input);
};

template <typename T>
void Foo::bar(const T& input)
{
    bar_impl<
        T,
        typename std::is_same<T, std::string>::type,
        typename std::is_integral<T>::type,
        typename std::is_same<T, bool>::type
    >::bar(input);
}

#endif // A_HPP

// b.cpp
#include "a.hpp"

int main()
{
  Foo foo;

  foo.bar(Foo());
  foo.bar(std::string());
  foo.bar(bool());
  foo.bar(int());
  return 0;
}
\$\endgroup\$
6
\$\begingroup\$

What you've written does work, and you seem to understand all the concepts (e.g., "you can't partially specialize function templates"), and your coding style (indentation and whatnot) seems fine.


Well, one coding-style improvement: You need those (void)input casts to shut up compiler warnings about unused parameters... in C. But in C++ you don't need those casts; what you do instead is, if you're not going to use a parameter, just don't give it a name.

static void bar(const T&) { ... }

However, your code doesn't really match any common TMP patterns — which is probably just because you're noodling around, learning, instead of trying to solve a specific problem.

If you just wanted to produce the same output with less code, you would do this:

struct Foo
{
    template<class T> auto bar(const T&) -> std::enable_if_t<!std::is_integral<T>::value>
    {
        std::cout << "Generic" << std::endl;
    }

    template<class T> auto bar(const T&) -> std::enable_if_t<std::is_integral<T>::value>
    {
        std::cout << "Specialization for integral type" << std::endl;
    }

    void bar(bool)
    {
        std::cout << "Specialization for bool" << std::endl;
    }

    void bar(const std::string&)
    {
        std::cout << "Specialization for std::string" << std::endl;
    }
};

int main()
{
    Foo foo;
    foo.bar(Foo());
    foo.bar(std::string());
    foo.bar(bool());
    foo.bar(int());
    return 0;
}

That is, your code is equivalent to providing two overloads of foo.bar, plus a couple of mutually exclusive templates (one for integral types and one for other types). The specific overloads for bool and std::string will take precedence over the templates.


Alternatively, you could use tag dispatch, as follows. (But I don't recommend it in this case because it turns out to be surprisingly subtle. Replace the second instance of std::true_type with ... and watch that overload silently drop out. I'm not actually sure why.)

struct Foo
{
    template<class T> void bar_impl(const T&, ...) {
        std::cout << "Generic" << std::endl;
    }

    template<class T> void bar_impl(const T&, std::true_type) {
        std::cout << "Specialization for integral type" << std::endl;
    }

    void bar_impl(const std::string&, ...) {
        std::cout << "Specialization for std::string" << std::endl;
    }

    void bar_impl(const bool&, std::true_type) {
        std::cout << "Specialization for bool" << std::endl;
    }

    template<class T> void bar(const T& input)
    {
        bar_impl(input, std::is_integral<T>{});
    }
};

Maybe the best approach would be to use two non-interacting levels of tag dispatch: first, is T integral? and second, what is T exactly?

template<bool IsIntegral> struct BarImpl {
    template<class T> static void bar(const T&) {
        std::cout << "Generic" << std::endl;
    }
    static void bar(const std::string&) {
        std::cout << "Specialization for std::string" << std::endl;
    }
};

template<> struct BarImpl<true> {
    template<class T> static void bar(const T&) {
        std::cout << "Specialization for integral type" << std::endl;
    }
    static void bar(bool) {
        std::cout << "Specialization for bool" << std::endl;
    }
};

struct Foo {
    template<class T> void bar(const T& input) {
        BarImpl<std::is_integral<T>::value>::bar(input);
    }
};
\$\endgroup\$
  • 1
    \$\begingroup\$ Regarding (void)input; to silence compiler: your advice is sound, but clang and g++ both complain about unnamed parameters when warning levels are cranked up. I typically compile with -Wall -Wextra -pedantic for example. \$\endgroup\$ – Edward Mar 15 '16 at 20:10
  • \$\begingroup\$ "clang and g++ both complain about unnamed parameters when warning levels are cranked up" — I've checked Clang 3.0 through 3.9, and GCC 4.3.6 through 5.2.0, and no they don't. If you're getting a compiler warning, post it here or as a question on StackOverflow and I bet someone will sort it out for you. \$\endgroup\$ – Quuxplusone Mar 15 '16 at 20:36
  • \$\begingroup\$ Hmm. Just checked for myself and you're right. I thought I had encountered that situation recently, but it's clear that I remembered it wrong. Thanks for correcting me! (Ah -- it's for C but not C++ that this comes up.) \$\endgroup\$ – Edward Mar 15 '16 at 20:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.