c++visual-c++c++-conceptsc++23

Instantiation of the function definition concept-constrained template


I am trying to implement the template using globally overloaded function to perform custom actions per signature. Since there could be group of types (eg. numeric types) having a similar implementation I tried to use concepts to cover it, but stumbled into the problem where the compiler doesn't see the definition with concept without explicit instantiation.

MSVC 17.13.0 (/std:c++latest)

// my.hpp
template <typename value_type>
void bar(value_type value);

template <typename value_type>
void foo(value_type value)
{
    bar<value_type>(value);
}
// my.cpp
#include "my.hpp"
#include <print>
#include <type_traits>

template<std::integral value_type>
void bar(value_type value)
{
    std::println("bar(integral[{}])", value);
}

int main()
{
    //bar(5);
    foo(6);
}

The program above does not build because the linker doesn't see bar's definition:

unresolved external symbol "void __cdecl bar(int)" (??$bar@H@@YAXH@Z) referenced in function "void __cdecl foo(int)" (??$foo@H@@YAXH@Z)

Though, if I uncomment the line bar(5); in main(), it will obviously instantiate the concept-constrained template, and the result will be proper (but with extra output from direct call bar(5)):

bar(integral[5])

bar(integral[6])

Is there common pattern for solving such a task? Basically, I want that the template receiving the type was able to find the suitable definition of template function itself, void bar<int> in my case.


Solution

  • First,

    template <typename      value_type> void bar(value_type value); // #1
    template <std::integral value_type> void bar(value_type value); // #2
    

    are two different overloads.

    Second, so in

    template <typename value_type> void bar(value_type value); // #1
    
    template <typename value_type>
    void foo(value_type value)
    {
        bar<value_type>(value);
    }
    

    Unless ADL kicks in, bar<value_type>(value); refers to #1 (the only one visible). As you use value_type=int, ADL (Argument-Dependent Lookup) is not involved.

    If you add dummy parameter to enable ADL, then you might have:

    struct adl_enabler{};
    
    template <typename value_type>
    void foo(value_type value)
    {
        bar<value_type>(value, adl_enabler{});
    }
    
    template <std::integral value_type>
    void bar(value_type value, adl_enabler) // in same namespace than `adl_enabler`
                                            // (global namespace here)
    {
        std::println("bar(integral[{}])", value);
    }
    

    Demo