c++language-designoverload-resolution

Why can't C++ overload resolution deduced nested template types?


This purported duplicate explains the mechanism of why this isn't allowed and shows a corner case where it can't work, but fails to address the question of why C++ refuses to allow it in the cases where it could work.

tl;dr; I know that C++ can't do this and I know how to work around it via ADL and am not asking about that. What I don't know is why the language design chose to (or was forced to) disallow this.


The longer version:

The following code works in all but the last case:

template <class T>
struct A { struct R {}; };

template <>
struct A<double> { using R = int; };

void Fn(int);

template <class T>
void Fn(typename A<T>::R v);

////////////////////////////////

void Test() {
    A<double>::R d;
    A<int>::R v;

    Fn(0);  // Works; calls Fn(int)
    Fn(d);  // Wroks; calls Fn(int)
    Fn(v);  // Fails; expected to call Fn<int>(A<int>::R)
}

It fails because the language is unable to infer template argument T for the template overload of Fn in order to make typename A<T>::R into A<int>::R. For a human, in this case, it's plainly clear that T should be int, but the rules of C++ don't allow that inference to be made.

My actual question:

What motivated or forced the designer of C++ to impose that restriction?

I suspect that the answer is that "guessing" that a class template will have a nested type before actually resolving it is technically or philosophically problematic, or that implementing that logic in existing compilers was architecturally prohibitive. That said, only the first of those is a fundamental problem and I'm not actually seeing why any of those are necessarily true, and that suggests I'm missing something interesting.


BTW: unless Bjarne Stroustrup or someone who was on the C++ standards committee in the 90's sees this, it's likely the best answer I'll get is well informed speculation.


P.S.

Several people have pointed out that where A<R>::S is provided as a using alias (particularly by explicit specialization) the resulting proper/fully-resolved type may not mention A<> at all.

  1. Though a valid point, that's not the situation I'm asking about.
  2. That's only relevant under the assumption that types and type aliases must be treated indistinguishably during type deduction. That may be a choice that the standards committee made, but it's a choice rather than something forced on them.
  3. Even under the current situation and rules, explicit specialization and type aliases can result some bazaar and unexpected results in like this:
template<int I> struct Int;
template<> struct Int<0> { using T = int; };
template<> struct Int<1> { using T = long; };

template<int I>
struct C {
    using T = typename Int<I % 2>::T;

    static int Mine();
    friend int Get(T) { return C::Mine(); }
};

void Test() {
    C<0> c0; // Ok
    C<1> c1; // This doesn't break anything.
    C<2> c2; // Delete this and things work.
    /*
    error: redefinition of 'int Get(C<2>::T)'
    note: 'int Get(C<0>::T)' previously declared here
    */
}

Solution

  • In the general case, as discussed in the comments, it’s impossible to invert the metafunction Ttypename A<T>::R. In the case where there really is a nested type with that name, certainly it could be deduced; presumably that case was considered too narrow to be motivating, especially if users might not understand the limitations and brittleness of the feature.

    When a paper proposing to support the feasible case was presented, the feeling of the committee was that it would be a breaking change, in the somewhat unusual sense that it would allow deduction (and its associated powers of introspection) to apply to types previously insulated from such analysis. People didn’t want to have to rename their nested types (breaking ABI even given type aliases under the old names for source compatibility) to prevent a change in semantics, and the ability to do things like customize for a member of any specialization of a class template (via overloads or partial specializations) wasn’t considered compelling.