Search code examples
c++c++20type-deduction

Match template with Class Template Deduction Placeholder param


Trying to use string literals as non-type template parameters in GCC 10.1. Have the following code:

#include <cstddef>
#include <algorithm>
#include <iostream>

template<std::size_t n> struct fixed_string {
  constexpr fixed_string(char const (&s)[n]) {
    std::copy_n(s, n, this->el);
  }
  constexpr fixed_string(fixed_string<n> const& s) {
    std::copy_n(s.el, n, this->el);
  }
  constexpr bool operator==(fixed_string const&) const = default;
  constexpr auto operator<=>(fixed_string const&) const = default;
  char el[n];
};
template<std::size_t n> fixed_string(char const(&)[n])->fixed_string<n>;

template<fixed_string str>
class Base {
public:
  Base() {
    std::cout << str.el << std::endl;
  }
};

template<fixed_string str>
class Derived : public Base<str> {
};


int main(void) {
  Derived<"Hello World"> x;
}

Base on it's own works fine, trying to figure out how to pass string literals up the class hierarchy. I thought a copy constructor would work, and GCC spits out this lovely error message and laughs at my attempt:

error: no matching function for call to ‘fixed_string(fixed_string<...auto...>)’
note: candidate: ‘template<long unsigned int n> fixed_string(const fixed_string<n>&)-> fixed_string<n>’
constexpr fixed_string(fixed_string<n> const& s) {
                       ^~~~~~~~~~~~
note:   template argument deduction/substitution failed:
note:   mismatched types ‘const fixed_string<n>’ and ‘fixed_string<...auto...>’

Cool. So <...auto...> is GCC speak for a "Class Template Deduction Placeholder" and I don't know how to threaten the compiler to match that with my function calls. Does anyone know how to coerce GCC here? Or alternatively how to propagate string literals through a class hierarchy?

Fun fact, passing str.el to the Base template crashes GCC.


Solution

  • It is certainly a gcc bug. I have experienced it and found this workaround:

    template<std::size_t n> struct fixed_string {
        consteval fixed_string(char const (&s)[n]) {
            std::copy_n(s, n, this->el);
        }
        consteval fixed_string(const fixed_string<n>& s) = default;
        consteval bool operator==(fixed_string const&) const = default;
        consteval auto operator<=>(fixed_string const&) const = default;
        char el[n];
        static const std::size_t size = n;
    };
    template<std::size_t n> fixed_string(char const(&)[n])->fixed_string<n>;
    
    template<std::size_t n, fixed_string<n> str>
    class BaseImpl {
        public:
            BaseImpl() {
                std::cout << str.el << std::endl;
            }
    };
    
    template <fixed_string s>
    using Base = BaseImpl<s.size, s>;
    
    template<std::size_t n, fixed_string<n> str>
    class DerivedImpl : public BaseImpl<n, str> {
    };
    
    template <fixed_string s>
    using Derived = DerivedImpl<s.size, s>;
    
    int main(void) {
        Derived<"Hello World"> x;
    }
    

    Or alternatively

    template<fixed_string str>
    class Base {
        public:
            Base() {
                std::cout << str.el << std::endl;
            }
    };
    
    template <std::size_t n, fixed_string<n> s>
    using BaseWrapper = Base<s>;
    
    template<fixed_string str>
    class Derived : public BaseWrapper<str.size, str> {
    };
    

    The general idea is to avoid using a deduced parameter to deduce another one.