简体   繁体   中英

How is nested template specialization done C++

I have a templated function defined as:

template<typename TObject> TObject Deserialize(long version, const Value &value)

what I need to do, is to write a specialization which would take vector defined as:

template<typename TNum, int cnt> class Vec

and still has access to cnt and TNum .

I have unsuccesfully tried

template<typename TNum, int cnt> Vec<TNum, cnt> Deserialize<Vec<TNum, cnt>>(long version, Value &value)

resulting in error: illegal use of explicit template arguments

What is the correct way to do it?

Usually, the correct answer to dealing with function templates and needing to partially specialize them, is to simply overload them instead. In this case this trick doesn't work directly because there are no arguments that depend on the template parameter, ie the template parameter is explicitly specified and not deduced. However, you can forward along to implementation functions, and make overloading work by using a simple tag struct.

#include <functional>
#include <iostream>
#include <type_traits>
#include <vector>
#include <array>

template <class T>
struct tag{};

template<typename TObject> 
TObject Deserialize_impl(long version, tag<TObject>) {
    std::cerr << "generic\n";
    return {};
}

template<typename T, std::size_t N> 
std::array<T,N> Deserialize_impl(long version, tag<std::array<T,N>>) {
    std::cerr << "special\n";
    return {};
}

template<typename TObject> 
TObject Deserialize(long version) {
    return Deserialize_impl(version, tag<TObject>{});
}


int main() {
    Deserialize<int>(0);
    Deserialize<std::array<int,3>>(0);

    return 0;
}

Live example: http://coliru.stacked-crooked.com/a/9c4fa84d2686997a

I generally find these approaches strongly preferable to partial specialization of a struct with a static method (the other major approach here) as there are many things you can take advantage with functions, and it behaves more intuitively compared to specialization. YMMV.

While the functional tag-dispatch is a nice approach, here's a class specialization version for comparison. Both have their use, and I don't think either is an inherently regrettable decision but maybe one matches your personal style more. For any class you write that needs a custom deserialize handler, just write a specialization of the Deserializer class:

#include <iostream>
#include <string>
using namespace std;

using Value = std::string;

// default deserialize function 
template <typename TObject>
struct Deserializer {
    static TObject deserialize(long version, const Value &value) {
        std::cout << "default impl\n";
        return TObject();
    }
};

// free standing function (if you want it) to forward into the classes
template <typename TObject>
TObject deserialize(long version, const Value &value) {
    return Deserializer<TObject>::deserialize(version, value);
}

// Stub example for your Vec class
template<typename TNum, int cnt> class Vec { };

// Stub example for your Vec deserializer specialization
template <typename TNum, int cnt> struct Deserializer<Vec<TNum, cnt>> {
    static auto deserialize(long version, const Value &value) {
        std::cout << "specialization impl: cnt=" << cnt << "\n";
        return Vec<TNum, cnt>();
    }
};

int main() {
    Value value{"abcdefg"};
    long version = 1;

    deserialize<int>(version, value);
    deserialize<Vec<int, 10>>(version, value);
}

Ideally in this situation, Vec should reflect its own template parameters as members Vec::value_type and Vec::size() which should be constexpr .

If the class fails to provide its own properties in its own interface, the next best thing is to define your own extension interface. In this situation, you can have separate metafunctions (like accessor functions), or a traits class (like a helper view class). I'd prefer the latter:

template< typename >
struct vector_traits;

template< typename TNum, int cnt >
struct vector_traits< Vec< TNum, cnt > > {
    typedef TNum value_type;
    constexpr static int size = cnt;
};

template<typename TVec> TVec Deserialize(long version, Value &value) {
    typedef vector_traits< TVec > traits;
    typedef typename traits::value_type TNum;
    constexpr static int cnt = traits::size;
    …
}

This solution fits into any existing function, and even makes the signatures cleaner. Also, the function is more flexible because you can adapt it by adding traits specializations instead of entire new overloads.

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