How to fix gcc warning "friend declaration declares a non-template function"
Asked Answered
L

2

2

So I have some code here that compiles with gcc, clang, and msvc:

#include <cstdio>
#include <type_traits>

struct c_class;

template <class T> struct holder { friend auto adl_lookup(holder<T>); };

template <class C, class T> struct lookup {
  friend auto adl_lookup(holder<T>) { return holder<C>{}; }
};

struct cpp_class : lookup<cpp_class, c_class *> {
  cpp_class() {}
};

int main() {
  static_assert(std::is_same<holder<cpp_class>,
                             decltype(adl_lookup(holder<c_class *>{}))>{},
                "Failed");
}

The reason adl_lookup is defined in the lookup class instead of the holder class is so that you can do a "reverse" lookup from c_class to cpp_class when you inherit from the CRTP class lookup<cpp_class, c_class *>. So the friend function can't be moved to the holder class.

However, on gcc I get a warning about non template friend function:

<source>:9:37: warning: friend declaration 'auto adl_lookup(holder<T>)' declares a non-template function [-Wnon-template-friend]
    9 |     friend auto adl_lookup(holder<T>);
      |                                     ^
<source>:9:37: note: (if this is not what you intended, make sure the function template has already been declared and add '<>' after the function name here)

If I try to fix this by forward declaring the function and then using <>, it doesnt compile with gcc or msvc(although it does compile with clang):

#include <cstdio>
#include <type_traits>

struct c_class;

template <class T> struct holder;

template <class T> auto adl_lookup(const holder<T> &);

template <class T> struct holder {};

template <class C, class T> struct lookup {
  friend auto adl_lookup<>(const holder<T> &) { return holder<C>{}; }
};

struct cpp_class : lookup<cpp_class, c_class *> {
  cpp_class() {}
};

int main() {
  static_assert(std::is_same<holder<cpp_class>,
                             decltype(adl_lookup(holder<c_class *>{}))>{},
                "Failed");
}

Am I using standard-compliant C++ here(in both snippets)? Is there a reason to be concerned about gcc's warning about non-template friend or is it just a false positive that I can safely ignore?

Liv answered 23/2, 2022 at 1:47 Comment(3)
In the first snippet, why is adl_lookup declared twice?Kutenai
It looks like you do mean to stamp out a definition of a non-template function whenever lookup is instantiated. This is an unusual thing to want to do (hence the warning), but occasionally useful.Alcestis
@BrianBi I think without the declaration in holder ADL wouldn't find it.Quelpart
Q
6

The second snippet is ill-formed, because a friend declaration cannot be a definition of a template specialization. An open clang bug report for accepting this is here.

The first one seems valid to me.

The warning by GCC is annoying, because defining a non-template function as friend is what you want to do here. Unfortunately I don't think there is any way to indicate in code that this is really what you want to do, but you can disable the warning with -Wno-non-template-friend. According to the documentation it is there for historical reasons, to identify pre-ISO-C++ compatibility issues where the syntax had a different meaning.

You should be aware that the ability to use friend injections of this kind to enable stateful metaprogramming may be considered unintended feature of the language and could maybe (I don't know) be restricted at some point in the future, see this question.

Quelpart answered 23/2, 2022 at 3:57 Comment(2)
Is this really considered stateful metaprogramming? I am not trying to capture some state, but rather make lookup<C, T> an associated class for holder<T> for ADL, since you cannot declare namespace-scope functions inside a class.Liv
@PaulFultzII Maybe not, but I wanted to add the warning, since this method is often used for that purpose. In your case you must however still be careful about ODR violations if you use this in multiple translation units. For example if you have lookup<int, double> instantiated in one translation unit and lookup<long, double> in another, then the definitions of adl_lookup(const holder<double> &) violate ODR. (In a single translation unit it will cause a redefinition error.)Quelpart
L
0

It is a common mistake, as using if (var = foo()) instead if (var == foo()) even if the code are legal.

Whereas adding extra parents allow to "inform" compiler of intent is the one expected in the =/== case.

if ((var = foo()))

There are no trick syntax to tell that it is really a non-template function that you want to use.

There are still traditional #pragma way to disable warning in specific region for given compiler (see how-to-disable-gcc-warnings-for-a-few-lines-of-code)

Lati answered 23/2, 2022 at 9:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.