简体   繁体   中英

C++ template function call seems to prefer the less specialized one

I'm trying to simplify some of my earlier JSON serialization by using C++ templates to somewhat reduce boilerplate code. All went fine, until I wanted to serialize types such as lists, since these are templates themselves as well and seem to require partial template specialization, which does not seem to exist for template functions.

Therefore, I applied a neat little trick I found over here: https://www.fluentcpp.com/2017/08/15/function-templates-partial-specialization-cpp/

namespace Support {
// These are in header files
template <typename T>
struct convertType{};

// Specialised template that serialises a list by iterating over its members
template <typename T>
QJsonValue toJsonValue(const QList<T> &source, convertType<QList<T>>) {
    QJsonArray result;
    for (auto it = source.cbegin(); it != source.cend(); it++) {
        result.push_back(*it);
    }
    return result;
}

// "Fallback" template that generates an assertion
template <typename T>
QJsonValue toJsonValue(const T &source, convertType<T>) {
    // This function should never be called.
    std::string msg = "toJsonValue called with unimplemented type ";
    msg += typeid (T).name();
    Q_ASSERT_X(false, "toJsonValue<T>", msg.c_str()); // Always asserts. 
    return QJsonValue();
}

// Convenience function
template<typename T>
QJsonValue toJsonValue(const T &source) {
    return toJsonValue<T>(source, convertType<T>{});
}

// This one is in the CPP file

// Integer
template <>
QJsonValue toJsonValue(const int &source, convertType<int>) {
    return QJsonValue(source);
}

} // NS Support

When I call this template with an integer as follows:

qDebug() << Support::toJsonValue<int>(3713); // if not familiar with Qt, think as qDebug() being the same as std::cout.

This outputs "QJsonValue(double, 3713)", as expected.

However, when I try to pass a list as follows:

QList<int> foo = {1, 2, 3};
qDebug() << Support::toJsonValue<QList<int>>(foo);

The code will take, the in my eyes, least specialized function template, the one that generates an assertion. I have no idea why on earth this is happening, as I would expect that it would take the function template specialized for the QList .

Does anyone have an idea why this is happening? Am I perhaps misusing templates?

I have no idea why on earth this is happening, as I would expect that it would take the function template specialised for the QList.

Does anyone have an idea why this is happening?

When you call

Support::toJsonValue<QList<int>>(foo);

the following "convenience function" matches

template<typename T>
QJsonValue toJsonValue(const T &source) {
    return toJsonValue<T>(source, convertType<T>{});
}

and T is QList<int> .

So the internal call

toJsonValue<T>(source, convertType<T>{})

become

toJsonValue<QList<int>>(source, convertType<QList<int>>{});
// ........^^^^^^^^^^^^
// ........^^^^^^^^^^^^  here is the problem

where you explicitly impose that the T type is QList<int>

So the call can't matches your QList specialization

template <typename T>
QJsonValue toJsonValue(const QList<T> &source, convertType<QList<T>>)

where the template parameter T is intended to be int , the template argument of QList .

Your call can only match the fallback specialization

template <typename T>
QJsonValue toJsonValue(const T &source, convertType<T>)

Suggestion: lets works the template deduction. I mean: do not explicit the template argument in the internal call

template<typename T>
QJsonValue toJsonValue(const T &source) {
    return toJsonValue(source, convertType<T>{}); 
} // ......^^^^^^^^^^^
  // ......^^^^^^^^^^^ no more "<T>" after toJsonValue

so both the two-argument toJsonValue() function match (the Qlist version with T that is deduced as int and the fallback version with T that is deduced as QList<int> ) but the more specialized (the QList version) should be selected.

Does this two are equivalent? It seems like you create a overload instead of a partial specialization.

// Integer
template <>
QJsonValue toJsonValue(const int &source, convertType<int>) {
    return QJsonValue(source);
}
// Integer
template <>
QJsonValue<int> toJsonValue(const int &source, convertType<int>) {
    return QJsonValue(source);
}

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