4

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.

  • Possible duplicate of [How to check for the type of a template parameter?](https://stackoverflow.com/questions/13636540/how-to-check-for-the-type-of-a-template-parameter) – scohe001 Oct 25 '19 at 20:41
  • I guess you have to use SFINAE for that, because there is no way to consider class methods and global functions as overloads. But before that, I suggest considering if this is really needed. Why do you need some overloads in global scope and some in class scope? – Yksisarvinen Oct 25 '19 at 20:45
  • @scohe001 Hmmm... no, thanks for the answer, but that's not what I asked at all. =) – Wagner Volanin Oct 25 '19 at 20:47
  • @Yksisarvinen Custom encoding methods. Imagine, for example, a class that serializes to json. Inside the class I have a templated operator=() that calls to_json(int), to_json(double), and all basic data types... But the user should be able to create their own to_json(struct User1). – Wagner Volanin Oct 25 '19 at 20:49
  • Does the method in Print have to be called PrinterInstance? Or does it just have to be replacible by something called PrinterInstance? (I suspect you over-constrained your solution space) – Yakk - Adam Nevraumont Oct 25 '19 at 21:20
  • @Yakk - Adam Nevraumont, I am unable to see what you're hinting at. It has to be method with a known name (i.e. to_json(), as exemplified above) so another person using this class can create his/her own customized methods. – Wagner Volanin Oct 25 '19 at 21:25
  • @wagner Why does the method have to be called `to_json`? The free function that overrides its functionality should have that name, but the implementation in the class itself probably doesn't need to be (excrpt you have *required* it to have that name: **why?**). Your problem, however, restricts solutions to having a `to_json` method in the class *and* be the customization point; due to this restriction, all of your answers suck. I suspect this is an X/Y problem, where you think your problem is X but it actually is Y. – Yakk - Adam Nevraumont Oct 26 '19 at 00:07

3 Answers3

2

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);
 }
max66
  • 65,235
  • 10
  • 71
  • 111
1

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

LeDYoM
  • 949
  • 1
  • 6
  • 21
  • Nice! But I must do it in C++11... I'll add that to the original question. – Wagner Volanin Oct 25 '19 at 21:07
  • Then,notice you are not overloading anything, because the scope of the functions (and the number of parameters, the method has implicit this) in the two methods. I would recomend to have all in the same scope, so all methods or all member functions. – LeDYoM Oct 25 '19 at 21:13
  • You're, indeed, absolutely right. I think I'll consider changing all member funcions to methods in the same namespace, even with @max66 solution. – Wagner Volanin Oct 25 '19 at 21:19
0

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302