Check whether two functions are compatible using metaprogramming.

6152 views
Skip to first unread message

Rodolfo Lima

unread,
Feb 18, 2008, 7:06:11 PM2/18/08
to
Hi, I'm trying to come up with a way to check whether two functions
can be used interchangeably regarding its signature. Something like:

bool compatible = is_compatible<void(double), int(int)>::value

If they're compatible, I can write:

int test(int) {}
std::function<void(double)> f = test;

without a compiler error in std::function's assignment.

I've tried to use SFINAE without success. What I've tried: (I'm using c
++0x by the way, with gcc-4.3)

template <class SIG, class OBJ>
struct is_compatible : std::false_type
{
};

template <class R, class OBJ, class... ARGS>
struct is_compatible<R(ARGS...),decltype(OBJ(ARGS...))> :
std::true_type
{
};

g++ chokes at decltype line with: template argument 2 is invalid

What should I do?

Regards,
Rodolfo Lima.


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Thomas Lehmann

unread,
Feb 19, 2008, 8:59:02 AM2/19/08
to
On 19 Feb., 01:06, Rodolfo Lima <rodl...@gmail.com> wrote:
> Hi, I'm trying to come up with a way to check whether two functions
> can be used interchangeably regarding its signature. Something like:
>

How about the solutions afterwards; There're two I'm using:

1) typeid. Sometimes I need to have the concrete type as an unique id!
2) Meta programming. I just want to know wether two types
are the same or not!

struct UniqueTypeBase
{
static unsigned int _ids;
};

unsigned int UniqueTypeBase::_ids = 0;

template <class T>
struct UniqueType : public UniqueTypeBase
{
static const unsigned int id()
{
static unsigned int typeId = 0;
if (typeId == 0)
typeId = ++_ids;

return typeId;
}
};

/// comparing to types to be the same
template <class T, class U>
struct SameType { enum { valid = false }; };

/// comparing to types to be the same
template <class T>
struct SameType<T, T> { enum { valid = true }; };


// test functions
void test1(int){}
void test2(int){}
void test3(double){}

template <class U, class V>
bool sameTypes1(const U&, const V&)
{ return UniqueType<U>::id() == UniqueType<V>::id(); }

template <class U, class V>
bool sameTypes2(const U&, const V&)
{ return SameType<U, V>::valid; }

int main()
{
std::cout << "void (int): type is " << UniqueType<void
(int)>::id() << std::endl;
std::cout << sameTypes1(test1, test2) << std::endl; // 1
std::cout << sameTypes1(test2, test3) << std::endl; // 0
std::cout << sameTypes2(test1, test2) << std::endl; // 1
std::cout << sameTypes2(test2, test3) << std::endl; // 0
return 0;

Rodolfo Lima

unread,
Feb 20, 2008, 12:43:58 AM2/20/08
to
On 19 fev, 10:59, Thomas Lehmann <t.lehm...@rtsgroup.net> wrote:

> 2) Meta programming. I just want to know wether two types
> are the same or not!

Unfortunately that's not what I really meant by using two functions
interchangeably. If you make a string comparison of, say, void(*)
(double) and int(*)(int), your metafunction will say that they're
(rightfully) different. But say I have:

int test(int a) { return a; }

std::function<void(double>> f = a;

It is valid and compiles, although the function signatures are
different. I want a metafunction that tells me whether we can set a
particular std::function to another function which signatures are
different, but 'compatible', as is in this case.

My real issue is with function overloading. Suppose I have two
functions:
void func(std::function<void(int)> f) {}
void func(std::function<void(int,int)> f) {}

The correct overload would only be resolved if the parameter I'm
passing is a std::function with the correct signature. If I pass, for
instance, a 'int test(int) {}' pointer to func, instead of resolving
to the first overload, the compiler will say the call is ambiguous. I
want to disambiguate this by using SFINAE, by removing the unwanted
overload from the overload set.

Regards,
Rodolfo Lima.

Greg Herlihy

unread,
Feb 21, 2008, 8:47:29 AM2/21/08
to
On Feb 18, 4:06 pm, Rodolfo Lima <rodl...@gmail.com> wrote:
> Hi, I'm trying to come up with a way to check whether two functions
> can be used interchangeably regarding its signature. Something like:
>
> bool compatible = is_compatible<void(double), int(int)>::value
>
> If they're compatible, I can write:
>
> int test(int) {}
> std::function<void(double)> f = test;
>
> without a compiler error in std::function's assignment.

The C++ compiler rejects the assignment precisely because the
signature of the function being assigned is not compatible with the
signature expected. And circumventing this compiler error would do
nothing to change the fact that a function expecting a double
parameter cannot be called as if it were a function expecting an int
parameter. On the majority of architectures, ints and doubles have
very different formats and even require different calling conventions
- so the likelihood that such a function call could possibly work - is
close to nothing.

Now, it is possible in C++ to pass a int parameter to a function
expecting a double. But in that case, the C++ compiler must implicitly
convert the int argument into a double (and does so based on its
knowledge of the target function's signature). So unless the C++
compiler has accurate information about a function's actual signature,
the C++ compiler will not be able to make the necessary conversions of
the arguments. So, in the example above, the function f() expecting a
double would not actually be passed a double - but an int. And
whatever happens to the program after that point would be unlikely to
be good.

> What should I do?

You should assign a function pointer whose signature is compatible
with the one expected.

Greg

Rodolfo Lima

unread,
Feb 21, 2008, 11:29:46 PM2/21/08
to
> The C++ compiler rejects the assignment precisely because the
> signature of the function being assigned is not compatible with the

Actually it is valid C++ code, and compiles well with gcc-4.3 with c+
+0x extensions. This could be compiled using boost::function in c++98.
We're not dealing with the case where we're treating an int as if it
were a double, as in:
int a = 4;
double b = *(double *)&a;

In the example I've made, he compiler generates code to convert the
parameter to the expected type before calling the function If this
conversion isn't possible, an error is generated. That's what I've
meant about 'compatible signatures', ones which parameters can be
converted to the destination signatures.

To exemplify this case, we have:
int func(int a) { return a; }
std::function<void(double)> f = a;
a(5.5);

The compiler will generate something like
func((int)5.5);
And the return value will be discarded.

Regards,
Rodolfo Lima.

David Abrahams

unread,
Feb 28, 2008, 6:09:51 PM2/28/08
to
on Wed Feb 20 2008, Rodolfo Lima <rodlima-AT-gmail.com> wrote:

> On 19 fev, 10:59, Thomas Lehmann <t.lehm...@rtsgroup.net> wrote:
>
>> 2) Meta programming. I just want to know wether two types
>> are the same or not!
>
> Unfortunately that's not what I really meant by using two functions
> interchangeably. If you make a string comparison of, say, void(*)
> (double) and int(*)(int), your metafunction will say that they're
> (rightfully) different. But say I have:
>
> int test(int a) { return a; }
>
> std::function<void(double>> f = a;
>
> It is valid and compiles, although the function signatures are
> different. I want a metafunction that tells me whether we can set a
> particular std::function to another function which signatures are
> different, but 'compatible', as is in this case.
>
> My real issue is with function overloading. Suppose I have two
> functions:
> void func(std::function<void(int)> f) {}
> void func(std::function<void(int,int)> f) {}
>
> The correct overload would only be resolved if the parameter I'm
> passing is a std::function with the correct signature. If I pass, for
> instance, a 'int test(int) {}' pointer to func, instead of resolving
> to the first overload, the compiler will say the call is ambiguous. I
> want to disambiguate this by using SFINAE, by removing the unwanted
> overload from the overload set.

It's is easy to do for function pointers: you just peel apart the type
using partial specialization and check that int is convertible to each
parameter type, but there's nothing that will tell you whether an
arbitrary function object qualifies. Consider:

struct f
{
void operator()(std::string) const {}
};

f x;
func(x);

Do you care about cases like that one?


--
Dave Abrahams
Boost Consulting
http://boost-consulting.com

Roman.Pe...@gmail.com

unread,
Feb 29, 2008, 1:42:56 PM2/29/08
to
On 29 Feb, 00:09, David Abrahams <d...@boost-consulting.com> wrote:
> It's is easy to do for function pointers: you just peel apart the type
> using partial specialization and check that int is convertible to each
> parameter type, but there's nothing that will tell you whether an
> arbitrary function object qualifies. Consider:
>
> struct f
> {
> void operator()(std::string) const {}
> };
>
> f x;
> func(x);
>
> Do you care about cases like that one?

It's possible to do that for function objects. There is an article in
Russian that describes the technique used to implement metafunction
is_call_possible. http://www.rsdn.ru/forum/message/2759773.1.aspx

Here is slightly modified version ('func' replaced with 'operator()').

Example:

struct Foo
{
void operator()(double) const {}
void operator()(std::string) const {}
};

int main()
{
STATIC_ASSERT((is_call_possible<Foo, void(double)>::value));
STATIC_ASSERT((is_call_possible<Foo, void(int)>::value));
STATIC_ASSERT((is_call_possible<Foo, void(const char *)>::value));
STATIC_ASSERT((!is_call_possible<Foo, void(void *)>::value));
}

Implementation of is_call_possible:
template <typename Type>
class has_member
{
class yes { char m;};
class no { yes m[2];};

struct BaseMixin
{
void operator()(){}
};

struct Base : public Type, public BaseMixin {};

template <typename T, T t> class Helper{};

template <typename U>
static no deduce(U*, Helper<void (BaseMixin::*)(), &U::operator()>*
= 0);
static yes deduce(...);

public:
static const bool result = sizeof(yes) == sizeof(deduce((Base*)
(0)));
};

namespace details
{
template <typename type>
class void_exp_result
{};

template <typename type, typename U>
U const& operator,(U const&, void_exp_result<type>);

template <typename type, typename U>
U& operator,(U&, void_exp_result<type>);

template <typename src_type, typename dest_type>
struct clone_constness
{
typedef dest_type type;
};

template <typename src_type, typename dest_type>
struct clone_constness<const src_type, dest_type>
{
typedef const dest_type type;
};
}


template <typename type, typename call_details>
struct is_call_possible
{
private:
class yes {};
class no { yes m[2]; };

struct derived : public type
{
using type::operator();
no operator()(...) const;
};

typedef typename details::clone_constness<type, derived>::type
derived_type;

template <typename T, typename due_type>
struct return_value_check
{
static yes deduce(due_type);
static no deduce(...);
static no deduce(no);
static no deduce(details::void_exp_result<type>);
};

template <typename T>
struct return_value_check<T, void>
{
static yes deduce(...);
static no deduce(no);
};

template <bool has, typename F>
struct impl
{
static const bool value = false;
};

template <typename arg1, typename r>
struct impl<true, r(arg1)>
{
static const bool value =
sizeof(
return_value_check<type, r>::deduce(
(((derived_type*)0)->operator()(*(arg1*)0),
details::void_exp_result<type>())
)
) == sizeof(yes);

};

// specializations of impl for 2 args, 3 args,..
public:
static const bool value = impl<has_member<type>::result,
call_details>::value;
};

Regards,
Roman Perepelitsa.

--

Rodolfo Lima

unread,
Feb 29, 2008, 4:16:08 PM2/29/08
to
On Feb 28, 8:09 pm, David Abrahams <d...@boost-consulting.com> wrote:
> It's is easy to do for function pointers: you just peel apart the type
> using partial specialization and check that int is convertible to each
> parameter type, but there's nothing that will tell you whether an
> arbitrary function object qualifies. Consider:
>
> struct f
> {
> void operator()(std::string) const {}
> };
>
> f x;
> func(x);
>
> Do you care about cases like that one?

Yes, the solution should be general. I've tried to trigger a SFINAE
with, for instance,
decltype(static_cast<RETURN_TYPE>(functor(ARGS_TYPE()...)) with no
luck. As I read somewhere, SFINAE is just triggered for simple type
substitutions. If the error is indirectly related to the substitution,
the compiler should generate an error. The rationale seems to be that
it would be difficult to implement in current compilers, that's sad.

Thanks anyhow.

Best Regards,
Rodolfo Lima.


--

Rodolfo Lima

unread,
Feb 29, 2008, 8:16:06 PM2/29/08
to
On Feb 29, 3:42 pm, "Roman.Perepeli...@gmail.com"

<Roman.Perepeli...@gmail.com> wrote:
> It's possible to do that for function objects. There is an article in
> Russian that describes the technique used to implement metafunction
> is_call_possible.http://www.rsdn.ru/forum/message/2759773.1.aspx

Thank you very much Roman. This indeed works and through variadic
template parameters I've managed to make it work with any function
signatures.
For the record, I should add that it works correctly regarding return
values, i.e, we can do:
struct Foo
{
int operator()() {}
char *operator()(int) {}
};
static_assert(is_call_possible<Foo, void()>::value)
static_assert(!is_call_possible<Foo, int(double)>::value)

With the author's permission, this handy metafunction could be added
to boost. I wish I spoke russian to ask the author...

Regards,
Rodolfo Lima.

Roman.Pe...@gmail.com

unread,
Mar 4, 2008, 4:24:24 PM3/4/08
to
> For the record, I should add that it works correctly regarding return
> values, i.e, we can do:
> struct Foo
> {
> int operator()() {}
> char *operator()(int) {}};
>
> static_assert(is_call_possible<Foo, void()>::value)
> static_assert(!is_call_possible<Foo, int(double)>::value)

Yes, also it correctly handles const function objects.

struct foo { void operator()(); };
static_assert(is_call_possible<foo, void()>::value);
static_assert(!is_call_possible<const foo, void()>::value);

> With the author's permission, this handy metafunction could be added
> to boost. I wish I spoke russian to ask the author...

I can contact the author if you really want to bother with
submission of this code to boost.

Regards,
Roman Perepelitsa.

Rodolfo Lima

unread,
Mar 4, 2008, 6:41:40 PM3/4/08
to
On Mar 4, 6:24 pm, "Roman.Perepeli...@gmail.com"
<Roman.Perepeli...@gmail.com> wrote:

> I can contact the author if you really want to bother with
> submission of this code to boost.

Thanks. I could do it provided that I knew his email address. We could
communicate in English.
I've made some simple specializations of his is_call_possible to cope
with general functions and member functions, and I'm currently trying
to make it work with boost::bind and boost::function.
This could be a nice addition to boost.

Thanks,
Rodolfo Lima.

Reply all
Reply to author
Forward
0 new messages