0

After the question Is possible to fix the iostream cout/cerr member function pointers being printed as 1 or true? I am trying to write a C++ 98 compatible way to print any function pointer.

For this, I am using a fake C++ "variadic" template, i.e., writing all function definitions up to n parameters. However my fake variadic only works for function pointer with 0 arguments as show on the next example: https://godbolt.org/z/x4TVHS

#include<iostream>

template<typename Return>
std::ostream& operator <<(std::ostream& os, Return(*pointer)() ) {
    return os << (void*) pointer;
}

template<typename Return, typename T0>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( const T0& t0 ) ) {
    return os << (void*) pointer;
}

template<typename Return, typename T0, typename T1>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( const T0& t0, const T1& t1 ) ) {
    return os << (void*) pointer;
}

void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main() {
    std::cout << "1. " << fun_void_void << std::endl;
    std::cout << "2. " << fun_void_double << std::endl;
    std::cout << "3. " << fun_double_double << std::endl;
}

// Prints:
//    1. funptr 0x100401080
//    2. funptr 1
//    3. funptr 1

If I write the equivalent version using C++11 real variadic templates, then everything works fine: https://godbolt.org/z/s6wdgp

#include<iostream>

template<typename Return, typename... Args>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( Args... ) ) {
    return os << (void*) pointer;
}

void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main() {
    std::cout << "1. " << fun_void_void << std::endl;
    std::cout << "2. " << fun_void_double << std::endl;
    std::cout << "3. " << fun_double_double << std::endl;
}

// Prints:
//    1. funptr 0x100401080
//    2. funptr 0x100401087
//    3. funptr 0x100401093

After analyzing the code, I noticed the only difference between then is that the types on the C++11 example are not const references. Then, I removed the constness and reference from the C++98 ones and it started working: https://godbolt.org/z/ZrF66b

#include<iostream>

template<typename Return>
std::ostream& operator <<(std::ostream& os, Return(*pointer)() ) {
    return os << (void*) pointer;
}

template<typename Return, typename T0>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( T0 ) ) {
    return os << (void*) pointer;
}

template<typename Return, typename T0, typename T1>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( T0, T1 ) ) {
    return os << (void*) pointer;
}

void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main() {
    std::cout << "1. " << fun_void_void << std::endl;
    std::cout << "2. " << fun_void_double << std::endl;
    std::cout << "3. " << fun_double_double << std::endl;
}

// Prints:
//    1. funptr 0x100401080
//    2. funptr 0x100401087
//    3. funptr 0x100401093

Why the function pointer templates definitions are not matching when they are const and/or references?

Evandro Coan
  • 8,560
  • 11
  • 83
  • 144
  • 1
    Because those are different types? Not sure why you're confused that `double(*)(double)` doesn't match `double(*)(const double&)` – NathanOliver Jan 29 '20 at 20:45
  • Can you please explain why this behavior is not what you expected? It seems pretty obvious that there are e.g. no types `Return` and `T0`, such that `Return(*)(const T0&)` is `double(*)(double)`. – walnut Jan 29 '20 at 20:59
  • I would not expect this behavior because when I am building a template function, the most generic way to describe its arguments would be `const Type& arg` (In new version of C++, I could use perfect forwarding with `const Type&& arg`). While here the only way to make it work is to use `Type arg`. Just with this signature it accepts any other function pointers types as `const Type arg` (the opposite of argument passing). – Evandro Coan Jan 29 '20 at 22:01
  • @user Maybe you have a misunderstanding of how templates work. The template parameter will be substituted for some type (either given explicitly or deduced in some way) and then the function is called as if it was a non-template function with the substituted parameters. If you were to give the template argument for the function call `operator<<(std::cout, fun_double_double)` explicitly, what type would you give for `T0` that you think would cause the function call to be well-formed? – walnut Jan 29 '20 at 22:38
  • Btw. `Type arg` as function parameter is more generic than `const Type& arg` or `Type&& arg`. You could give any type, whether reference or not, for `Type` explicitly and therefore get any parameter type with `Type arg`, but with `const Type& arg` you can only ever get `const` references as parameter type, even if you try to specify a type explicitly. It is just that reference types are never deduced automatically for `Type arg` and that it is more often intended to take parameters as references in generic code, especially if you don't know whether the types involved can be copied/moved. – walnut Jan 29 '20 at 22:47

1 Answers1

1
template<typename Return, typename T0>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( const T0& t0 ) ) {
  return os << (void*) pointer;
}
void f(int) {}
os << f;

this doesn't work because there is no types Return and T0 such that Return(*)(const T&) matches void(*)(int) exactly.

As an aside, overriding operators not in an associated namespace is bad, because it is very fragile. And you aren't allowed to override operators in std. So this plan is a bad one.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • When you say **`you aren't allowed to override operators in std`** you mean this **`std::ostream& operator <<(...)`**? Is there a reference explaining why I am not allowed to do this? – Evandro Coan Feb 01 '20 at 01:21
  • @user You aren't allowed to put anything in `namespace std` except a few template specializations. I mean `namespace std { std::ostream& operator<<( std::ostream& os, void(*)(int) ); }` makes your program ill-formed, no diagnostic required. Together with the rule that you should always overload operators in associated namespaces of one of the types involved means ... you shouldn't do this. – Yakk - Adam Nevraumont Feb 01 '20 at 01:32
  • @user `namespace A { struct Foo {}; std::ostream& operator<<( std::ostream& os, Foo const& ) { return os<<"FOO!"; }` within `namespace A` you cannot find the `<<` you defined, because the non-ADL lookup rules STOP once any `operator<<` is found, even ones that don't apply to your types in question. – Yakk - Adam Nevraumont Feb 01 '20 at 01:34