3

I am observing a weird behavior, which I cannot explain. The following code (a simplified version of the actual code) compiles correctly in gcc 11.4, and the argument "val" inside the read() method of MyStruct is implicitly converted to a Dummy using the converting constructor.

#include <iostream>

namespace secret_impl_space
{
  template <typename T>
  struct MyStruct
  {
    T val;
    MyStruct(T v): val(v) {}
    // the val below may be implicitly converted to Dummy if operator >> is missing from T
    virtual std::istream& read(std::istream& i) { i >> val; return i; }
  };

  struct Dummy { template<typename T> Dummy(const T& v){}; };

  inline std::istream& operator>>(std::istream& i, const Dummy& v)
  {
    std::cout << "Using Dummy" << std::endl; return i;
  }
}

struct A {int a;};

int main()
{
  A aa{1};
  secret_impl_space::MyStruct<A>* test (new secret_impl_space::MyStruct<A>(aa));
  return 0;
}

However, I found that newer gcc versions, starting from 12 on, give me the following compilation error (confirmed with godbolt):

no match for ‘operator>>’ (operand types are ‘std::istream’ {aka ‘std::basic_istream<char>’} and ‘A’)

The weirdest thing is that the code compiles correctly on any gcc version if I do one of the following two things:

  1. Get rid of the namespace "secret_impl_space"
  2. Remove the virtual specifier from the read() method.

Can someone explain this behavior? I am honestly puzzled.

Note: just to give the readers some context, in the original code MyStruct was the implementation part of a type-erasing container like boost::any - that is why it has a virtual >> method, to overload the one in the type-erased base interface. The whole idea behind defining the Dummy class was to allow using the type-erased container also for some types that do not have a >> operator - generating a Runtime warning instead of a compiler error. This is pretty terrible IMHO, but I did not write this, it was already around when I found the problem. All this machinery was 'hidden', for some reason (shame?), inside a namespace.

6
  • It's weird to see operator>> take its second argument by const reference. Is that intentional? Commented Sep 24, 2024 at 8:36
  • 1
    @Botje I honestly have no idea. I guess that who wrote this thought that, since the Dummy >> operator was just meant to write a warning, they could as well leave the Dummy const. But that is just a theory. As I said in the post, I am not the author - this is just a poorly written library I have to work with, which stopped compiling after a OS update. Commented Sep 24, 2024 at 8:48
  • godbolt.org/z/PEsnMqe7n MSVC compiles too and it should not. Commented Sep 24, 2024 at 9:00
  • BTW why const in reading stream operator? This is part of problem. Commented Sep 24, 2024 at 9:03
  • 1
    I have compiler warnings enabled. I'm seeing this compiler warning: 'operator>>' should be declared prior to the call site or in the global namespace Pretty much explains the problem. Commented Sep 24, 2024 at 13:25

1 Answer 1

6

By all means this shoudn't have been working. By ADL rule (aka incorrectly Koenig rule) for a function call in template context where matching function is not declared yet, name lookup is deferred until instantiation.

At point of instantiation, for a call with arguments std::basic_istream<char> and <global namespace>::A only namespace std and secret_impl_space should've been considered for look-up, considering undeclared converion or looking in global namespace (where A is defined) would be incorrect. The look-up is happening at point of instantiation. The error lists these types as a manner of reasoning.

For virtual function of template

point of instantiation is immediately following the point of instantiation of its enclosing class template specialization.

Prior to C++11 this was implementation-defined. Otherwise inline member function of template have their point of instantiation at same location as their ODR-use (and may not happen at all for unused ones). The operator definition in posted code is within the "gap" between these two.

The code can be fixed by moving declaration of Dummy and operator= above declaration of MyStruct. It can appear as a prototype:

namespace secret_impl_space
{
  struct Dummy { template<typename T> Dummy(const T& v){}; };

  std::istream& operator>>(std::istream& i, const Dummy& v);

  template <typename T>
  struct MyStruct
  {
    T val;
    MyStruct(T v): val(v) {}
   
    virtual std::istream& read(std::istream& i) { i >> val; return i; }
  };
  // at this point virtual MyStruct::read must be valid. 
  // If deleted or undeclared functions or types are ODR-used, 
  // the code of  MyStruct::read is ill-formed, IFNDR.

  inline std::istream& operator>>(std::istream& i, const Dummy& v)
  {
    std::cout << "Using Dummy" << std::endl; return i;
  }
}

struct A {int a;};

int main()
{
  A aa{1};
  // at this point a non-virtual member of MyStruct will be instantiated
  secret_impl_space::MyStruct<A>* test (new secret_impl_space::MyStruct<A>(aa));
  return 0;
}

The change of behavior possibly result of a fix for some flaws in ADL implementation.

Original code is also rejected by clang. Some versions of MSVC may accept it due to non-compliant beahviour of look-up starting in global namespace.

Sign up to request clarification or add additional context in comments.

7 Comments

Thank you for the answer. Can you point me to the ruling about which namespaces have to be searched by the compiler? Also, if this is incorrect, why does this still work in gcc without the virtual specifier? Is this a gcc bug?
@A.Manfreda Something like eel.is/c++draft/basic.lookup.unqual . While basic ruling was retained since C++98, for a non-virtual function local namespace IS considered (as part of "class's interface"), but I don't remember where the otherwise case is stated. This needs a language-lawyer tag. Main difference for virtual members of template is that theyare always instantiated (to avoid incorrect dynamic dispatch).
@Swift - Friday Pie the link you posted seems broken, at least to me (error 404).
@A.Manfreda I think, I ran into absolutely same problem this year when compiling code which was working in MSVC2017 ad GCC4, but didn't work in GCC12, exactly same issue, a virtual function,a free-standing visitor function and a late definition. I didn't question it, more like I lifted my brow at old code "why it worked before". There adding a function prototype and a helper class early was also the solution.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.