2

I have put together an example on creating a base template with a number of specializations.

#include <iostream>

template<typename T, typename U = void>
struct foo {
    static void apply() {
        std::cout << "a: " << __FUNCTION__ << std::endl;
    }
};

template<typename U>
struct foo<int, U> {
    static void apply() {
        std::cout << "b: " << __FUNCTION__ << std::endl;
    }
};

template<typename U>
struct foo<double, U> {
    static void apply() {
        std::cout << "c: " << __FUNCTION__ << std::endl;
    }
};

template<>
struct foo<double, double> {
    static void apply() {
        std::cout << "d: " << __FUNCTION__ << std::endl;
    }
};

template<typename T>
struct foo<T, std::enable_if_t<std::is_same_v<T, char>>> {
    static void apply() {
        std::cout << "e: " << __FUNCTION__ << std::endl;
    }
};

template<>
struct foo<short> {
    static void apply() {
        std::cout << "f: " << __FUNCTION__ << std::endl;
    }
};

template<>
struct foo<unsigned long, void> {
    static void apply() {
        std::cout << "g: " << __FUNCTION__ << std::endl;
    }
};

int main() {
    foo<long>::apply();
    foo<long, long>::apply();
    foo<int>::apply();
    foo<int, int>::apply();
    foo<double>::apply();
    foo<double, float>::apply();
    foo<double, double>::apply();
    foo<char>::apply();
    foo<short>::apply();
    foo<unsigned long>::apply();

    return 0;
}

When a specialization is defined and a template parameter is defined in the base template such as the template parameter U which is defaulted to void how is this propagated to the specializations. Is the done at the point of specialization as in the first specialization foo<int, U> and U must be void as it is unspecified and adopted from the base template?

Also with the

template<typename T>
struct foo<T, std::enable_if_t<std::is_same_v<T, char>>>

specialization, the enable_if_t yields the type void, why does this not collide with the base template and how is it considered more specialized?

Any additional quotes from the standard to complement answers are additionally welcome.

Blair Davidson
  • 901
  • 12
  • 35

1 Answers1

0

Default template arguments affect all template argument lists where their template parameter is not otherwise specified. That means that

template<>
struct foo<short>

is an explicit specialization of foo<short,void> just as foo<short>::apply() is a use of that specialization. It even applies to template argument deduction:

template<class T>
void bar(foo<T>);

deduces T from an argument of type foo<T,void>. (This is more interesting in the case where the default depends on T and users sometimes supply something else.)

Similarly,

template<class T>
struct foo<T*>  // not from the question's code

is a partial specialization of foo<T*,void>. Of course, one major use of default template arguments is to provide a SFINAE hook, as in the case of

template<typename T>
struct foo<T, std::enable_if_t<std::is_same_v<T, char>>>

Here, foo<char> matches this specialization because it's really foo<char,void>; it's possible to use the primary template instead by writing (say) foo<char,const void>, but clients are usually expected not to do that (sometimes by providing a type alias that exposes only the intended template parameter). Under that assumption, the specializations

template<>
struct foo<double, double>

is useless and

template<typename U>
struct foo<double, U>

is just the same as

template<>
struct foo<double>

except that the latter is not a template (so that errors are sometimes reported earlier and typename isn't needed for dependent names).

The determination that it is more specialized than the primary template does not involve the default template argument: it is based on trying to deduce T from foo<T,U>, not foo<T/*,void*/> ([temp.spec.partial.order]/1.2).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76