Home > Software engineering >  namespace usage is preventing template instantiation of overload
namespace usage is preventing template instantiation of overload

Time:10-13

I'm trying to instantiate an overloaded template function in a convoluted way, and I'm finding that it gets instantiated unless it's in a namespace. The compilation succeeds either way, but it fails to link if the function isn't instantiated.

I can fix the issue by reordering the program so that the overload is defined before the call, but that's actually what I'm trying to avoid.

I'm guessing this is related to ADL.

I've tried to create a minimal reproducable example - if I simplify anything further (eg replace SFyp with a native type) there ceases to be a distinction between namespaced and non-namespaced versions.


SZug<T, A>.zug() forwards to baz<T, A>() which forwards to foo<T, A>() (foo is overloaded later).

SFyp is simply a struct used for T (native types don't produce same behavior).

main() calls SZug<T, A>.zug()

If foo (and its overloads) are defined in a namespace, foo doesn't instantiate. Otherwise, it does.


ns_used.cpp

// Forwarding chain: SZug.zug() -> baz() -> foo().

namespace ns
    {
        template <typename T1, typename T2>
        void foo(T1 v1, T2 v2); // This is overloaded by SFyp
    }


template <typename T1, typename T2>
void baz(T1 v1, T2 v2)
    { ns::foo(v1, v2); }


template <typename T1>
struct SZug
    {
        template <typename T2>
        void zug(T2 v2)
            {
                T1 v1;
                baz(v1, v2);
            }
    };


// SFyp overload of foo().

struct SFyp{};

namespace ns
    {
        template <typename T2>
        void foo(SFyp fyp, T2 v2)
            {}
    }


// Test.

int main()
    {
        SZug<SFyp> z;
        z.zug(1);
    }

g -o ns_used.run ns_used.cpp fails to link:

clang -o ns_used.run ns_used.cpp fails to link:

in function `void baz<SFyp, int>(SFyp, int)':
ns_used.cpp:(.text._Z3bazI4SFypiEvT_T0_[_Z3bazI4SFypiEvT_T0_] 0xf): undefined reference to `void ns::foo<SFyp, int>(SFyp, int)'

ns_unused.cpp

// Forwarding chain: SZug.zug() -> baz() -> foo().

//namespace ns
//  {
        template <typename T1, typename T2>
        void foo(T1 v1, T2 v2); // This is overloaded by SFyp
//  }


template <typename T1, typename T2>
void baz(T1 v1, T2 v2)
    { /*ns::*/foo(v1, v2); }


template <typename T1>
struct SZug
    {
        template <typename T2>
        void zug(T2 v2)
            {
                T1 v1;
                baz(v1, v2);
            }
    };


// SFyp overload of foo().

struct SFyp{};

//namespace ns
//  {
        template <typename T2>
        void foo(SFyp fyp, T2 v2)
            {}
//  }


// Test.

int main()
    {
        SZug<SFyp> z;
        z.zug(1);
    }

g -o ns_unused.run ns_unused.cpp succeeds.

clang -o ns_unused.run ns_unused.cpp succeeds.

Note: They're identical except for commented-out namespacing.


And, as mentioned above, if I put SFyp and its foo overload before baz, it succeeds with or without namespacing.


What is the reason for this odd behavior? Is it ADL?

Is there any way to fix this so that my overload is considered when baz is looking for a foo, while still using namespacing?

CodePudding user response:

TLDR

ADL is for looking up unqualified function names like foo and not for qualified function names like ns::foo or ::foo.


What is the reason for this odd behavior?

The problem is that ADL can't happen for qualified names like ns::foo or ::foo. This is the reason if you replace foo with ::foo in your second example, then you'll get the exact same error. Demo. And you've provided only a declaration for the primary function template and not a definition and moreover you've provided the foo overload for SFyp after defining baz so that qualified lookup can't find that foo overload's definition. And as we now know that ADL won't be happening, essentially we get the linker error at the end for the missing definition of the primary function template since the qualified lookup only found a declaration and so was not able to instantiate a definition from that declaration. Meaning that only the declaration template<> void ns::foo<SFyp, int>(SFyp, int) was instantiated. This is exactly was the linker error says.

On the other hand, for an unqualified name like foo the ADL can work. This is why your second example works. Basically, in your 2nd example ADL will be happening so that the linker has the definition available and so there will be no error in your 2nd example.

There are 3 ways to solve this. The third way to fix this is simply provide a definition for the primary function template. Demo. The other two are given at the end of the answer.


This can be seen from ADL documentation:

Argument-dependent lookup, also known as ADL, or Koenig lookup [1], is the set of rules for looking up the unqualified function names in function-call expressions, including implicit function calls to overloaded operators.

(emphasis mine)

This means that since ns::foo and ::foo are qualified function named, ADL won't happen here. But if you were to use just the unqualified function name foo then ADL can work(as you noticed in your 2nd example).


The same can be seen from basic.lookup.argdep which says:

When the postfix-expression in a function call is an unqualified-id, other namespaces not considered during the usual unqualified lookup may be searched, and in those namespaces, namespace-scope friend function or function template declarations ([class.friend]) not otherwise visible may be found.

(emphasis mine)

This means that since in your first example the postfix expression ns::foo is a qualified-id so ADL can't work for that and you get the mentioned error. But in your second example foo is an unqualified-id so ADL can work here and so your 2nd example works.


Solutions

There are 2 ways of solving this both of which are shown below:

Method 1

Below is the working solution where i've forward declared SFyp in the global namespace and also declared the function template void foo(SFyp fyp, T2 v2) in the ns namespace so that the desired overload is called.

#include <iostream>
#include <type_traits>
#include <vector>


struct SFyp;    //I ADDED THIS
namespace ns
    {
        template <typename T1, typename T2>
        void foo(T1 v1, T2 v2); 
      
        template <typename T2>            //I ADDED THIS 
        void foo(SFyp fyp, T2 v2);
    }


template <typename T1, typename T2>
void baz(T1 v1, T2 v2)
    { ns::foo(v1, v2); }


template <typename T1>
struct SZug
    {
        template <typename T2>
        void zug(T2 v2)
            {
                T1 v1;
                baz(v1, v2);
            }
    };


// SFyp overload of foo().

struct SFyp{};

namespace ns
    {
        template <typename T2>
        void foo(SFyp fyp, T2 v2)
            {std::cout <<"desired overload called";} //I ADDED THIS
        
    }


// Test.

int main()
    {
        SZug<SFyp> z;
        z.zug(1);
    }

Demo

Output of the modified program is:

desired overload called

Method 2

The second method is to just move the foo overload before baz's definition as shown below:

namespace ns
    {
        template <typename T1, typename T2>
        void foo(T1 v1, T2 v2); 
    }
//-------------------I MOVED THIS HERE-----------------------------------//
struct SFyp{};

namespace ns
    {
        template <typename T2>
        void foo(SFyp fyp, T2 v2)
            {std::cout << "desired overload";}
    }
//------------------------------------------------------//
template <typename T1, typename T2>
void baz(T1 v1, T2 v2)
    { ns::foo(v1, v2); }


template <typename T1>
struct SZug
    {
        template <typename T2>
        void zug(T2 v2)
            {
                T1 v1;
                baz(v1, v2);
            }
    };



int main()
    {
        SZug<SFyp> z;
        z.zug(1);
    }

Working demo

  • Related