Is it possible to call a class method and a global method based on the parameters of a class template?

Viewed 102

This is my problem:

#include <string>



struct Print
{
    template <typename T>
    void Printer( const T& data )
    {
        PrinterInstance( data );
    }


    void PrinterInstance( const int& data )
    {
        printf( "INTEGER\n" );
    }
};



void PrinterInstance( const std::string& data )
{
    printf( "STRING\n" );
}



int main()
{
    Print print;
    print.Printer( "3" );
    return 0;
}

Inside the class Print, I have a template Printer that basically calls PrinterInstance based on the parameters of the template. Also, I should be able to extend this funcionality by adding more PrinterInstance outside of the class.

But this does not compile. If I implement PrinterInstance only inside the class, it's ok. If I implement PrinterInstance only outside the class, it's also ok. But as soon I have one PrinterInstance inside the class and one outside the class, the template will only try to use the class one.

Can I make this work?

EDIT: It has to work in C++11.

3 Answers

If you can add a template PrinterInstance() inside Print

template <typename T>
void PrinterInstance (T const & data)
 { ::PrinterInstance(data); }

that call the global PrinterInstance(), you have that the compiler use a method PrinterInstance() when the type is an exact match, the global version (passing through the template method) otherwise.

But, as in the LeDYoM's answer, this require that the global versions of PrinterInstance() are defined (or, at least, declared) before the Printer body.

The following is a full compiling example

#include <string>
#include <iostream>

void PrinterInstance ( const std::string& data )
 { std::cout << "STRING, " << data << std::endl; }

struct Print
 {
   template <typename T>
   void Printer (T const & data)
    { PrinterInstance( data ); }

   template <typename T>
   void PrinterInstance (T const & data)
    { ::PrinterInstance(data); }

   void PrinterInstance (int const & data)
    { std::cout << "INTEGER, " << data << std::endl; }
 };

int main ()
 {
   Print p;
   p.Printer("3");
   p.Printer(5);
 }

If you can use C++17:

#include <string>
#include <type_traits>

void PrinterInstance( const std::string& data );

struct Print
{
    template <typename T>
    void Printer( const T& data )
    {
        if constexpr (std::is_same_v<T,int>)
        {
            PrinterInstance( data );
        }
        else
        {
            ::PrinterInstance(data);
        }
    }

    void PrinterInstance( const int& data )
    {
        printf( "INTEGER\n" );
    }
};

void PrinterInstance( const std::string& data )
{
    printf( "STRING\n" );
}

int main()
{
    Print print;
    print.Printer( "3" );
    return 0;
}

Note: The free PrintInstance has to be declared first. (Or all the overloads you want). You can add to the if constexpr as many types as you need (overloads inside the struct). You can probably translate it to C++14 and some SFINAE.

Code: https://godbolt.org/z/S1ZJ93

Issue is that member hides functions at namespace scope.

one solution is to dispatch to the right function, something like:

void PrinterInstance( const std::string& data )
{
    printf( "STRING\n" );
}

// Declarations of PrinterInstance for built-in should also be visible before `Printer`

struct Print
{
    template <typename T>
    void Printer( const T& data )
    {
        if constexpr (std::is_invocable<decltype(&Print::PrinterInstance), Print, T>::value) {
            PrinterInstance( data );
        } else {
            using ::PrinterInstance; // make function at global scope visible,
                                     // hide member function and so allows ADL
            PrinterInstance( data );
        }
    }


    void PrinterInstance( const int& data )
    {
        printf( "INTEGER\n" );
    }
};

namespace N
{
    struct S{};

    void PrinterInstance( const N::S& )
    {
        printf( "S\n" );
    }
}

Demo

In C++11, it might be;

struct Print
{
    template <typename T>
    void Printer( const T& data );

    void PrinterInstance( char data )
    {
        printf( "CHAR\n" );
    }

    void PrinterInstance( const int& data )
    {
        printf( "INTEGER\n" );
    }
};

template <typename T>
auto printerImpl(Print& p, const T& arg) -> decltype(p.PrinterInstance(arg))
{
    return p.PrinterInstance(arg);
}

template <typename T>
auto printerImpl(Print&, const T& arg) -> decltype(PrinterInstance(arg))
{
    return PrinterInstance(arg);
}

template <typename T>
void Print::Printer( const T& data )
{
    printerImpl(*this, data);
}

Demo

Related