5

And how to fix the code?

Here is the code: https://godbolt.org/z/vcP6WKvG5

#include <memory>
#include <utility>

enum class Format {
    Number,
    Text,    
};

template <template <Format> typename Visitor, typename... Args>
void switchByFormat(Format format, Args&&... args) {
    switch(format) {
        case Format::Number: 
            Visitor<Format::Number>::visit(std::forward<Args>(args)...);
            break;
        case Format::Text: 
            Visitor<Format::Text>::visit(std::forward<Args>(args)...);
            break;
    }
}

struct AstNode {};

template <Format format>
struct ANode: public AstNode {};

using AstNodePtr = std::shared_ptr<AstNode>;

template <template <Format> typename AstNodeT, 
          typename... Args>
struct AstNodeFactory {
    template <Format format>
    struct Visitor {
        static void visit(AstNodePtr& result, Args&&... args)
        {
            result = std::make_shared<AstNodeT<format>>(std::forward<Args>(args)...);
        }
    };
};

template <template <Format> typename AstNodeT,              
          typename... Args>
AstNodePtr makeAstNode(Format format, Args&&... args)
{
    AstNodePtr result;
    switchByFormat<typename AstNodeFactory<AstNodeT, Args...>::Visitor>(format,
                                                                result,
                                                                std::forward<Args>(args)...);
    return result;
}

int main() {
    auto textAnode = makeAstNode<ANode>(Format::Text);
}

Clang link: https://godbolt.org/z/9fxWE9j71

The error in Clang is:

source>:45:64: error: typename specifier refers to class template member in 'AstNodeFactory<ANode>'; argument deduction not allowed here
    switchByFormat<typename AstNodeFactory<AstNodeT, Args...>::Visitor>(format,

GCC link: https://godbolt.org/z/4EvrzGWYE

GCC error:

In instantiation of 'AstNodePtr makeAstNode(Format, Args&& ...) [with AstNodeT = ANode; Args = {}; AstNodePtr = std::shared_ptr<AstNode>]':
<source>:52:53:   required from here
<source>:45:5: error: 'typename AstNodeFactory<ANode>::Visitor' names 'template<enum class Format format> struct AstNodeFactory<ANode>::Visitor', which is not a type
   45 |     switchByFormat<typename AstNodeFactory<AstNodeT, Args...>::Visitor>(format,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cigien
  • 57,834
  • 11
  • 73
  • 112
Alex Jenter
  • 4,324
  • 4
  • 36
  • 61

1 Answers1

4

Since Visitor is a template class rather than a type, you need to specify the template keyword

switchByFormat<AstNodeFactory<AstNodeT, Args...>::template Visitor>(
                                                //^^^^^^^^
  format, result, std::forward<Args>(args)...);

Demo.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 1
    The OP is also asking why MSVC accepts the code, and whether it's valid. OP has added the [language-lawyer] tag as well. – cigien Jan 11 '22 at 14:27
  • Wow, so simple! Thanks a ton. And yes, it would be interesting to know why MSVC is more lenient here – Alex Jenter Jan 11 '22 at 14:32
  • 4
    MSVC historically performs [two-phase name lookup](https://stackoverflow.com/q/7767626/1968) differently. As a consequence MSVC actually doesn’t require `typename` and `template` specifiers at all: it already *knows* whether a given identifier refers to a value, a type or (as in this case) a template. – Konrad Rudolph Jan 11 '22 at 14:34
  • 1
    But the standard doesn't require, only _allows_, `template` in this situation, doesn't it? Since no specialization is named. Isn't this what Davis Herring is talking about [here](https://stackoverflow.com/a/70635817/17732522) as also deprecated in C++23? – user17732522 Jan 11 '22 at 14:49