简体   繁体   中英

Template function specialization for template class

Is it possible to write something like this in C++11/14?

#include <iostream>
#include <vector>

template <typename T>
T Get();

template <typename T>
struct Data {
    std::vector<T> data;
};

template <>
template <typename T>
Data<T> Get<Data<T>>() {
    return Data<T>{{T{}, T{}}};
}

template <>
template <typename T>
std::vector<T> Get<std::vector<T>>() {
    return std::vector<T>(3);
}

int main() {
    std::cout << Get<Data<int>>().data.size() << std::endl;  // expected output is 2
    std::cout << Get<std::vector<int>>().size() << std::endl; // expected output is 3
    return 0;
}

Overloading won't help in this case, since call to Get<...>() will be ambiguious ( see ):

template <typename T>
Data<T> Get() {
    return Data<T>{{T{}, T{}}};
}

template <typename T>
std::vector<T> Get() {
    return std::vector<T>(3);
}

Any direction on how to overcome this are welcome.

There is workaround, that gives you something like this: do not specialize - overload:

#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename T>
size_t Get(const T& data)
{
    return 444;
}

template <typename T>
struct Data
{
    std::vector<T> data;
};

template <typename T>
size_t Get(const Data<T>& data) {
    return data.data.size();
}

int main() {
    std::cout << Get<>(0) << std::endl;  // expected output is 444
    std::cout << Get<>(Data<int>{}) << std::endl;  // expected output is 0
    return 0;
}

Output:

444
0

Note, that size_t Get(const Data<T>& data) is not a specialization - it is completely "different" Get() , that is called for argument of type Data<T> for any T .

Here you can see working sample.


EDIT

I see you changed your question completely. However, I will still try to answer it. There is a standard workaround for lack of partial function specialization - using delegation to structs/classes.

Here is what you need:

#include <iostream>
#include <vector>

using namespace std;

template <typename T>
struct GetImpl;

template <typename T>
struct Data {
    std::vector<T> data;
};

template <typename T>
struct GetImpl< Data<T> >
{
    static Data<T> Get() {
        return Data<T>{ {T{}, T{}} };
    };
};

template <typename T>
struct GetImpl< std::vector<T> >
{
    static std::vector<T> Get() {
        return std::vector<T>(3);
    };
};

int main() {
    std::cout << GetImpl< Data<int> >::Get().data.size() << std::endl;  // expected output is 2
    std::cout << GetImpl< std::vector<int> >::Get().size() << std::endl; // expected output is 3
    return 0;
}

Output:

2
3

Working sample can be found here.


If you don't like the syntax, you can make it a little bit shorter, by changing static function Get() to function call operator:

template <typename T>
struct Get< Data<T> >
{
    Data<T> operator()() {
        return Data<T>{ {T{}, T{}} };
    };
};

template <typename T>
struct Get< std::vector<T> >
{
    std::vector<T> operator()() {
        return std::vector<T>(3);
    };
};

And then:

Get< Data<int> >()().data.size();
Get< std::vector<int> >()().size();

You have only two extra characters - () . This is the shortest solution I can think of.

As Columbo mentioned in his comment, you should apply the standard workaround for lack of partial specialization support for functions: delegation to a partially specialized class:

template <typename T>
struct GetImpl;

template <typename T>
T Get() { return GetImpl<T>::Do(); }

and now use partial specialization on struct GetImpl<T> { static T Do(); } struct GetImpl<T> { static T Do(); } instead of Get<T>()

But it would be impossible for compiler to distinguish Get<Data<int>> from Get<Data<Data<int>>> .

It's not impossible. If that's something you need to do, we can add separate overloads:

template <typename T>
size_t Get(const Data<T>& data);

template <typename T>
size_t Get(const Data<Data<T>>& data); // preferred for Data<Data<int>>

Or if what you want is to only overload for the non-nested case, we can add a type trait and use SFINAE:

template <typename T> struct is_data : std::false_type { };
template <typename T> struct is_data<Data<T>> : std::true_type { };

template <typename T>
enable_if_t<!is_data<T>::value, size_t>
Get(const Data<T>& data);

That way, the call with Data<Data<int>> would call the generic Get(const T&) . Or, if you want that case to not compile at all:

template <typename T>
size_t Get(const Data<T>& data) {
    static_assert(!is_data<T>::value, "disallowed");
    ...
}

So overloading gives you lots of options. Specialization gives you none, since it's disallowed anyway.

Following delegation to the struct's way you can implement more general approach: you can use structs to check the container type and inner type like this:

#include <iostream>
#include <vector>

template <typename T>
struct Data {
    std::vector<T> data;
};

template <template <typename...> class Container, typename>
struct get_inner;

template <template <typename...> class Container, typename T>
struct get_inner<Container, Container<T>>
{
    typedef T type;
};




template <typename T, typename U = typename get_inner<Data, T>::type>
Data<U> Get() {
    return Data<U>{ {U{}, U{}} };
}

template <typename T, typename U = typename  get_inner<std::vector, T>::type>
std::vector<U> Get() {
    return std::vector<U>(3);
}

int main() {
    std::cout << Get<Data<int>>().data.size() << std::endl;  // expected output is 2
    std::cout << Get<std::vector<int>>().size() << std::endl; // expected output is 3
    return 0;
}

http://coliru.stacked-crooked.com/a/90b55767911eff0e

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