Non-dependent name lookup in C++
Recently I cam across a tricky compile-time problem when compiling with gcc a code normally developed with Visual Studio C++. The issue can be boiled down to the following example:
struct A {
double r;
operator const double &() const {return r;}
};
void f1(const int &r)
{}
void f1(const long &r)
{}
template <class X>
struct XX {
void operator() (X r)
{
// f1 is a non-dependent name
f1(r);
}
};
// The following declaration will be seen in any use of XX() below it,
// but not in GCC
void f1(const double &r)
{}
int main(void)
{
A a;
XX<double> x;
x(a);
}
The above will compile with Visual Studio C++ 2015 but will fail to compile on all modern versions of GCC with "ambiguous overload" error. The root cause of the error is the time and context at which names in templates are looked up:
- In Visual Studio C++ 2015 the name f1 in the operator() is looked up only in the main function, meaning that overload void f1(const double &r) is seen and selected
- In GCC the name f1 in the operator() is looked up when the template is defined, and therefore does not see void f1(const double &r) declaration. Instead only void f1(const int &r) and void f1(const long &r) are seen, and neither can be preferred ahead of the other
According to the current C++ standard (Section temp.nondep) , non-dependent names are looked-up and bound "at the point they are used" -- meaning at the point of the definition of the template. This requires so called "two-phase" name lookup which is only supported in the most recent 2017 versions of Visual Studio C++ as an optional compilation mode.
As a result all codes developed up to about 2017 with Visual Studio C++ may contain subtle bugs when moved to a compiler which does non-dependent name lookup according to the standard rules.