dhu
dhu

Reputation: 718

ambiguous template specialization in variadic function template

I am trying to write a variadic function template to calculate the byte size of a struct. This is going to be used in a network programming project I am working on. The first step I came up with this without the variadic template that works:

#include <cstdint>
#include <iostream>

struct Position
{
    int32_t x;
    int32_t y;
};

template<typename T>
inline std::size_t sizeT() { return sizeof(T); }

template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }
// if I change the definition of Position, I need to remember to change this function

int main(int argc, char* argv[])
{
    std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
    std::cout << "Position:" << sizeT<Position>() << std::endl;

    return 0;
}

I compiled with g++ 9.3.0 on my Ubuntu 20.04 laptop and it worked fine. However if I try to make it a variadic template then I ran into different compilation errors. The point of this variadic template is to be able to write like the following, so that the implementation doesn't depend on the explicit knowledge of x and y's types.

Here is bad code #1

#include <cstdint>
#include <iostream>

struct Position
{
    int32_t x;
    int32_t y;
};

template<typename T>
inline std::size_t sizeT() { return sizeof(T); }

template<>
inline std::size_t sizeT<Position>()
{
    return sizeT<decltype(Position::x), decltype(Position::y)>();
}

template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }


int main(int argc, char* argv[])
{
    std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
    std::cout << "Position:" << sizeT<Position>() << std::endl;

    return 0;
}

I got

error: ambiguous template specialization 'sizeT' for 'std::size_t sizeT()

note: candidate are: 'template std::size_t sizeT()'

note: template<class T, class... Ts> std::size_t sizeT()'

Bad code #2. If I put the variadic template at a different location:

#include <cstdint>
#include <iostream>

struct Position
{
    int32_t x;
    int32_t y;
};

template<typename T>
inline std::size_t sizeT() { return sizeof(T); }

template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }


template<>
inline std::size_t sizeT<Position>()
{
    return sizeT<decltype(Position::x), decltype(Position::y)>();
}

int main(int argc, char* argv[])
{
    std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
    std::cout << "Position:" << sizeT<Position>() << std::endl;

    return 0;
}

I got

error: call of overloaded 'sizeT<int8_t>()' is ambiguous

note candidate: 'std::size_t sizeT() [with T=signed char; std::size_t = long unsigned int]'

note candidate: 'std::size_t sizeT() [with T=signed char; Ts={}; std::size_t = long unsigned int]'

error: call of overloaded 'sizeT()' is ambiguous

note candidate: 'std::size_t sizeT() [with T=Position; std::size_t = long unsigned int]'

note candidate: 'std::size_t sizeT() [with T=Position; Ts={}; std::size_t = long unsigned int]'

I kinda understand case #2 but I don't understand #1. How can I achieve this? Thank you so much in advance.

Upvotes: 0

Views: 149

Answers (1)

Jarod42
Jarod42

Reputation: 218323

You don't use variadic...

You might do (C++17)

template <typename ... Ts>
constexpr std::size_t sizeT() { return (0 + ... + sizeof(Ts)); }

template <>
constexpr std::size_t sizeT<Position>()
{
    return sizeT<decltype(Position::x), decltype(Position::y)>();
}

Demo

But sizeT<Position, Position> would return 2 * sizeof(Position) instead of 2 * sizeT<Position> (which are identical here).

You might do instead:

template <typename... Ts>
struct tag{};

template <typename T>
constexpr std::size_t sizeT(tag<T>) { return sizeof(T); }

template <typename ... Ts>
constexpr std::size_t sizeT(tag<Ts...>) { return (0 + ... + sizeT(tag<Ts>())); }

template <typename ... Ts>
constexpr std::size_t sizeT(tag<Ts>...) { return (0 + ... + sizeT(tag<Ts>())); }

constexpr std::size_t sizeT(tag<Position>)
{
    return sizeT(tag<decltype(Position::x), decltype(Position::y)>());
}

Demo

Upvotes: 1

Related Questions