Search code examples
c++c++11variadic-templatesconstexprtemplate-meta-programming

Couldn't deduce template paramter even if it is known at compile time


I have a variadic template class SomeClass that looks like this (basically):

template<std::size_t SIZE_>
class SomeClass {
public:
    static constexpr std::size_t SIZE = SIZE_;
};

It has a constexpr member that simply contains the std::size_t template parameter used to instantiate it. I want a constexpr function that can sum all the sizes of SomeClass<SIZE_> specializatins. My first idea was to make a simple variadic template function that adds all the SIZEs like that:

template<typename T>
constexpr std::size_t totalSize() {
    return T::SIZE;
}

template <typename T, typename... Ts>
constexpr std::size_t totalSize() {
    return totalSize<T>() + totalSize<Ts...>();
}

Now I try to call it:

constexpr std::size_t size = totalSize<SomeClass<1>, SomeClass<2>, SomeClass<3>>()    // should return 6

It turns at that the last parameter unpacking makes call to totalSize<T> function ambiguous because both template totalSize functions match it. Fine, I modified my code to have two distinct functions and came up with this:

template<typename T>
constexpr std::size_t totalSizeSingle() {
    return T::SIZE;
}

template <typename T, typename... Ts>
constexpr std::size_t totalSize() {
    std::size_t result = totalSizeSingle<T>();
    if (sizeof...(Ts) > 0) result += totalSize<Ts...>();
    return result;
}

Again, the last unpacking seem to be problematic. Apparently, T can't be deduced even though it is known at compile time. I get the following error (compiling with GCC):

In instantiation of 'constexpr std::size_t totalSize() [with T = SomeClass<3>; Ts = {}; std::size_t = long long unsigned int]':
main.cpp:129:51:   required from here
main.cpp:134:82:   in 'constexpr' expansion of 'totalSize<SomeClass<1>, SomeClass<2>, SomeClass<3> >()'
main.cpp:129:51:   in 'constexpr' expansion of 'totalSize<SomeClass<2>, SomeClass<3> >()'
main.cpp:129:58: error: no matching function for call to 'totalSize<>()'
  129 |         if (sizeof...(Ts) > 0) result += totalSize<Ts...>();
      |                                          ~~~~~~~~~~~~~~~~^~
main.cpp:127:23: note: candidate: 'template<class T, class ... Ts> constexpr std::size_t totalSize()'
  127 | constexpr std::size_t totalSize() {
      |                       ^~~~~~~~~
main.cpp:127:23: note:   template argument deduction/substitution failed:
main.cpp:129:58: note:   couldn't deduce template parameter 'T'
  129 |         if (sizeof...(Ts) > 0) result += totalSize<Ts...>();
      |                                          ~~~~~~~~~~~~~~~~^~

I don't really understand why it is not working. Every type is know at compile time. Why is this happening and how can I get it to work? I'm using C++11.


Solution

  • There's a reason people call structs metafunctions in c++ template metaprogramming.

    In our case the major advantage of doing metaprogramming in a struct is that resolving function overloading by template arguments is different and more limited than that of class specialisation.

    So I suggest:

    template <class ...> // only called if there's no arguments
    struct add_sizes {
        static constexpr auto value = 0;
    };
    
    template <class T, class ... Ts>
    struct add_sizes <T, Ts...> {
        static constexpr auto value = T::SIZE + add_sizes<Ts...>::value;
    };
    
    // then wrap it into your function:
    
    template <class ... Ts>
    constexpr std::size_t totalSize () {
        return add_sizes<Ts...>::value;
    }
    

    demo


    Another advantage of using structs is for easy "return" of multiple values or types, which for functions gets complicated. Sometimes by calculating A you've already calculated B, so it can make sense to store both.

    In general I'd say that if you're ever struggling solving a metaprogramming problem with functions, switch to structs.