Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templated conversion function to function pointer

Yay, another question title composed of a random sequence of C++ terms!

Usually we make a class Callable by implementing operator(). But you can also do so by implementing a user-defined conversion to function pointer or reference type. Instead of using perfect forwarding, a conversion function can return a pointer to a function which is then called with the original argument list.

struct call_printf {
    typedef int printf_t( char const *, ... );
    operator printf_t & () { return std::printf; }
};

http://ideone.com/kqrJz

As far as I can tell, the typedef above is a syntactic necessity. The name of a conversion function is formed from a type-specifier-seq, which does not allow a construct like int (*)(). That would require an abstract-declarator. Presumably the reason is that such type names get complicated, and complex constructs used as object names are tough to parse.

Conversion functions are also allowed to be templated, but the template arguments must be deduced, because there is nowhere to explicitly specify them. (That would defeat the whole point of implicit conversion.)


Question #1: In C++03, is there was no way to specify a function conversion operator template? It appears there was no way to resolve the template arguments (i.e., name them in a deduced context) in an acceptable function pointer type.

Here is the equivalent reference from C++11, §13.3.1.1.2/2 [over.call.object]. It's substantially the same from C++03:

In addition, for each non-explicit conversion function declared in T of the form

operator conversion-type-id () cv-qualifier attribute-specifier-seqopt;

where cv-qualifier is the same cv-qualification as, or a greater cv-qualification than, cv, and where conversion-type-id denotes the type “pointer to function of (P1,...,Pn) returning R”, or the type “reference to pointer to function of (P1,...,Pn) returning R”, or the type “reference to function of (P1,...,Pn) returning R”, a surrogate call function with the unique name call-function and having the form

R call-function ( conversion-type-id F, P1 a1, ... ,Pn an) { return F (a1,... ,an); }

is also considered as a candidate function. Similarly, surrogate call functions are added to the set of candidate functions for each non-explicit conversion function declared in a base class of T provided the function is not hidden within T by another intervening declaration.


Question #2: In C++11, can such a conversion be specified using a default template argument? This is useful for SFINAE. The only difference here from the above example is that the conversion-type-id only represents a function reference after instantiation, because it's a dependent type (despite invariance). This trips up GCC and it skips the member template.

enum { call_alternate = true; }

struct call_switch {
    template< bool en = call_alternate >
    operator typename std::enable_if< en, decltype( fn_1 ) & >::type ()
        { return fn_1; }

    template< bool en = ! call_alternate >
    operator typename std::enable_if< en, decltype( fn_2 ) & >::type ()
        { return fn_2; }
};

We also have alias templates. It seems that alias substitution occurs before instantiation, given the example in §14.5.7/2, where the declarations of process conflict. In GCC 4.7, this code at least instantiates the declaration, but then it produces a bizarre "candidate expects 2 arguments, 2 provided" error.

template< typename t >
using fn_t = void (&)( t );

struct talk {
    template< typename t >
    operator fn_t< t >() { return fn; }
};

int main() {
    talk()( 3 );
}
like image 512
Potatoswatter Avatar asked Jun 13 '12 15:06

Potatoswatter


1 Answers

Question #1: In C++03, is there was no way to specify a function conversion operator template? It appears there was no way to resolve the template arguments (i.e., name them in a deduced context) in an acceptable function pointer type.

Yes, that is correct.

Question #2: In C++11, can such a conversion be specified using a default template argument?

It can, and you can also use alias templates, but you cannot use such a conversion function template for creating surrogate call functions. You can use it for converting your class object to function pointers in implicit conversions otherwise.

We also have alias templates. It seems that alias substitution occurs before instantiation, given the example in §14.5.7/2, where the declarations of process conflict. In GCC 4.7, this code at least instantiates the declaration, but then it produces a bizarre "candidate expects 2 arguments, 2 provided" error.

Yes, this is https://groups.google.com/forum/?fromgroups#!topic/comp.std.c++/lXLFBcF_m3c (and caused closure of DR395), but even though such a conversion function template can work in cases like void(&p)() = yourClassObject, it won't work for surrogate call functions, because there the conversion function needs to provide a fixed non-dependent type that the class object is converted to when the surrogate function is called, but a conversion function template does not provide such a type normally (weird things like template<typename = int> operator Identity<void(*)()>(); aside...).

I think that GCC may incorrectly generates the candidate call-function(void (&)( t ), t) with the dependent types still there and try to call that candidate, thereby violating some invariant of it (which could explain the weird error message - possibly hitting an } else { ... } unexpectedly somewhere).

like image 150
Johannes Schaub - litb Avatar answered Oct 23 '22 03:10

Johannes Schaub - litb