0

I have got somewhat of a follow-up question to the scenario I described here, I found this post especially helpful for reasoning about my problem.

Background

In my original question, I was interested in overloading the std::make_shared function to deal with partially specified template arguments:

std::make_shared<Container<1>::Internal>(arguments...);

Internal and Container are structs, which both possess one template argument, the one of Internal is derived. (The full code is available below). The accepted solution works perfectly when specifying the template argument of Container directly (1 in the line above).

Issue / Question

Then I tried to specify the template argument from a differentconstexpr context:

std::make_shared<Container<A>::Internal>(arguments...);

where A is a template argument of a third struct in which the above line is located. Here I realised, that

  1. ... In the constructor, the line does not work and has to be changed into std::make_shared<Container<A>::template Internal>(arguments...);
  2. ... In a member function the line works fine.

Question

Why is that the case?

Code

The code can be tried out here.

#include <memory>
#include <type_traits>

/// Container that is used in order to partially specify template arguments
template <int A> struct Container {
  /// Contained type, of which the template arguments are deduced.
  template <int B> struct Internal {
    explicit Internal(std::integral_constant<int, B> fu) { (void)fu; }
  };
};

/// Own make shared function / overload
namespace std {
template <template <int> typename partial, typename... Args> auto make_shared(Args &&... args) {
  using target_type = decltype(partial{std::declval<Args>()...});
  return std::make_shared<target_type>(std::forward<Args>(args)...);
}
} // namespace std

/// Some struct which uses its own template argument in order to create
template <int A> struct Producer {
  template <typename... T> explicit inline Producer(T &&... t) noexcept {
    auto const works = std::make_shared<Container<1>::Internal>(std::forward<T>(t)...);
    // XXX: the following fails if "template" is omitted
    auto const fails = std::make_shared<template Container<A>::Internal>(std::forward<T>(t)...);
  }

  template <typename... T> static auto test(T &&... t) noexcept {
    /// XXX: Here, it works without specifying "template"
    return std::make_shared<Container<A>::Internal>(std::forward<T>(t)...);
  }
};

int main() { Producer<1>{std::integral_constant<int, 8>{}}; }

Compilation

  • Compiler used: g++-9.1 and g++-10.0.
  • Compilation command: g++ -std=c++2a sol.cc

Error

The following error message is generated when I omit 'template' in the constructor.

sol.cc: In instantiation of ‘Producer<A>::Producer(T&& ...) [with T = {std::integral_constant<int, 8>}; int A = 1]’:
sol.cc:34:58:   required from here
sol.cc:25:29: error: dependent-name ‘Container<A>::Internal’ is parsed as a non-type, but instantiation yields a type
   25 |     auto const fails = std::make_shared<Container<A>::Internal>(std::forward<T>(t)...);
      |                        ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sol.cc:25:29: note: say ‘typename Container<A>::Internal’ if a type is meant

Own thoughts on the Issue

During name lookup, the compiler determines whether the name denotes type / template or not.

If a name cannot be looked up until the actual template arguments are known it is a dependent-name. From what I understand that is the case here (at least in the constructor). Hence, in the constructor, the compiler is unable to determine that Internal denotes a template, whereas in the static member function, it is.

This might be the reason for Container<A>::Internal being a dependent-name in the constructor, in case the type of the structure is to be automatically deduced by the compiler. But in my use case I explicitly state it (Producer<1> in main()).

Note

I tried to provide a simpler example, but in that example this error did not occur, I think what I provided above is relatively minimal.

mutableVoid
  • 1,284
  • 2
  • 11
  • 29
  • 1
    Try adding a statement that calls the static function. Your code explicitly constructs an object, so will call the constructor, which forces the compiler to diagnose the code of the constructor, but does not call the static function anywhere. The handling of templates is reasonably complicated, and some compilers defer template instantiation quite aggressively - which can affect when or if the compiler has enough information to issue diagnostics – Peter Jan 24 '20 at 21:28

1 Answers1

1

In the code you've provided, the test function is not called anywhere, therefore it never gets instantiated. If it were to be instantiated, then as a result of the missing template, the compiler would be required to diagnose it as ill-formed.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312