2

Consider the following simple construct. I derive a class type EntityView from Entity which allows me to specify an allocator if I want to, and if I don't it should fall back to the defaulted template parameter. Yet the compiler complains that it can't derive template arguments:

Demo

#include <iostream>
#include <stdexcept>
#include <string>
#include <memory>

template <typename StringType = std::string>
struct Entity {
    Entity(int) {
        std::cout << "Hello World!" << std::endl;
    }
};

template <typename Allocator = std::allocator<int>>
struct EntityView : public Entity<std::basic_string<char, std::char_traits<char>, Allocator>> {
    using Entity<std::basic_string<char, std::char_traits<char>, Allocator>>::Entity;
};

int main() {
    EntityView myview{2};
}

Yields (gcc):

<source>: In function 'int main()':
<source>:19:24: error: class template argument deduction failed:
   19 |     EntityView myview{2};
      |                        ^
<source>:19:24: error: no matching function for call to 'EntityView(int)'
<source>:14:8: note: candidate: 'template<class Allocator> EntityView()-> EntityView<Allocator>'
   14 | struct EntityView : public Entity<std::basic_string<char, std::char_traits<char>, Allocator>> {
      |        ^~~~~~~~~~
<source>:14:8: note:   template argument deduction/substitution failed:
<source>:19:24: note:   candidate expects 0 arguments, 1 provided
   19 |     EntityView myview{2};
      |                        ^
<source>:14:8: note: candidate: 'template<class Allocator> EntityView(EntityView<Allocator>)-> EntityView<Allocator>'
   14 | struct EntityView : public Entity<std::basic_string<char, std::char_traits<char>, Allocator>> {
      |        ^~~~~~~~~~
<source>:14:8: note:   template argument deduction/substitution failed:
<source>:19:24: note:   mismatched types 'EntityView<Allocator>' and 'int'
   19 |     EntityView myview{2};
      |                        ^

The problem seems to be that it is trying to do CTAD, however there's nothing to be derived from the constructor, also not in the inherited class. Is there any way to have the compiler just accept the default template argument instead of trying to deduce it?

glades
  • 3,778
  • 1
  • 12
  • 34
  • You can either write `EntityView<>` or define a custom deduction guide. – maxplus Jul 03 '23 at 15:00
  • @maxplus Ok, but that would mean I would need to write a deduction guide for every ctor that I import into my derived class right? – glades Jul 03 '23 at 15:03

1 Answers1

4

Just define a universal deduction guide:

#include <iostream>
#include <stdexcept>
#include <string>
#include <memory>

template <typename StringType = std::string>
struct Entity {
    Entity(int) {
        std::cout << "Hello World!" << std::endl;
    }
    template <typename T>
    Entity(T) {
        std::cout << "template" << std::endl;
    }
    template<typename... Args>
    Entity(Args&&...) {
        std::cout << "variadic" << std::endl;
    }
};

template <typename Allocator = std::allocator<int>>
struct EntityView : public Entity<std::basic_string<char, std::char_traits<char>, Allocator>> {
    using Entity<std::basic_string<char, std::char_traits<char>, Allocator>>::Entity;
};

template<typename... Args>
EntityView(Args&&...) -> EntityView<>;

int main() {
  { EntityView myview{2}; }
  { EntityView myview{2.}; }
  { EntityView myview{2, 3}; }
}
maxplus
  • 502
  • 1
  • 12
  • It works well for the contrived version but I still can't use it in my more advanced case: https://godbolt.org/z/c3roM36nc :( – glades Jul 03 '23 at 15:40
  • @glades, I see. Template parameter can't deduce an initializer list (or anything) from a brace-init-list. You can replace variadic template with variadic arguments: `EntityView(...) -> EntityView<>;` is the whole definition of an alternative version of a universal deduction guide. – maxplus Jul 03 '23 at 15:49
  • Disclaimer to my previous comment: I couldn't find any reference as to whether such usage is correct. It would be incorrect to pass brace-init-list to e.g. a constructor defined similarly, since brace-init-list is not an object and can't be passed to a function. Perhaps msvc is right to reject such a deduction guide. In that case, I'd say there is no way to implement a standard-conforming universal deduction guide accepting a brace-init-list, same as there is no way to "forward" a brace-init-list to base class constructor without creating an object from it (and thus restricting its type). – maxplus Jul 03 '23 at 16:10
  • I'm inclined to assume that gcc and clang implemented this behaviour for this exact case. If that is so we can only hope for standardisation to force MSVC to this behaviour as well. Initialization and initializer_list are also c++'s s weak spot imho. Anyway, CTAD wouldn't even be necessary if I could just typedef my View like so: https://godbolt.org/z/WsfeM16zz. But it seems typedefs don't allow for default template arguments? You wouldn't happen to know a way around this restriction? – glades Jul 04 '23 at 08:40
  • @glades Two other workarounds: a) have separate names for non-template version (e.g. through a type alias) and template version that accepts an allocator type b) wrap the two initializer list entries in another `{}` and optionally remove the universal deduction guide, this seems to allow `<>` omission rules to take precedence over CTAD. But all options except explicit `<>` and `a)` handle only variable declaration/initialization and construction of a temporary, they won't work for class members or function arguments. – maxplus Jul 04 '23 at 09:41
  • In your case I would probably use two aliases, template `HTTP_HeadersWithAllocator` and plain `HTTP_HeadersView`. – maxplus Jul 04 '23 at 09:50
  • 1
    I actually opted for the same solution they use in the stdlib now. I renamed the class to `basic_http_headers`, then typedefed my `basic_http_headers_view` over it and used typedefs `HTTP_Headers = basic_http_headers<>` and `HTTP_HeadersView = basic_http_headers_view<>`. The user can customize his allocator by using the basic_* type/typedef – glades Jul 05 '23 at 07:50