SFINAE и обнаружение, если объект функции C ++ возвращает void - PullRequest
7 голосов
/ 27 декабря 2010

Я читал различные авторитеты по этому вопросу, включая Дьюхерст , но пока не смог найти ни одного ответа на этот, казалось бы, простой вопрос.1005 * call a C ++ объект функции , (в основном, все, что вы можете вызвать, чистая функция или класс с ()), и вернуть его значение, если это не void, или "true"иначе.

using std:

struct Foo {
  void operator()() { cout << "Foo/"l; }
};
struct Bar {
  bool operator()() { cout << "Bar/"; return true; }
};

Foo foo;
Bar bar;
bool baz() { cout << "baz/"; return true; }
void bang() { cout << "bang/"; }

const char* print(bool b) { cout << b ? "true/" : "false/"; }

template <typename Functor> bool magicCallFunction(Functor f) {
  return true;  // Lots of template magic occurs here 
                // that results in the functor being called.
}

int main(int argc, char** argv) {
  print(magicCallFunction(foo));
  print(magicCallFunction(bar));
  print(magicCallFunction(baz));
  print(magicCallFunction(bang));
  printf("\n");
}
// Results:  Foo/true/Bar/true/baz/true/bang/true

ОБНОВЛЕНИЕ

Спасибо за мысли и идеи!

Исходя из этого, я фактически решил поднять все свои шаблоны на один уровень - поэтому вместо этого у меня есть:

bool eval(bool (*f)()) { return (*f)(); }

bool eval(void (*f)()) { (*f)(); return true; }

template <typename Type>
bool eval(Type* obj, bool (Type::*method)()) { return (obj->*method)(); }

template <typename Type>
bool eval(Type* obj, void (Type::*method)()) { (obj->*method)(); return true; }

и общие классы для переноса различных объектов и методов.Спасибо мистеру Ри за код, который подтолкнул меня в этом направлении!

Ответы [ 6 ]

6 голосов
/ 05 апреля 2011

Чтобы обнаружить пустое возвращаемое значение во время компиляции, стандартным приемом является перегрузка operator,.Крутая вещь с оператором запятой заключается в том, что он может принимать параметр void, и в этом случае по умолчанию используется встроенный operator,.В коде:

template <typename> tag {};

template <typename T>
tag<T> operator,(T, tag<void>);

Теперь expr, tag<void>() имеет тип tag<typeof(expr)>, даже если expr имеет тип void.Затем вы можете поймать это с помощью обычных трюков:

char (&test(tag<void>))[1];
template <typename T> char (&test(tag<T>))[2];

template <typename F>
struct nullary_functor_traits
{
    static const bool returns_void = sizeof(test((factory()(), tag<void>()))) == 1;
private:
    static F factory();    
};
1 голос
/ 27 декабря 2010

Разве не было бы проще реализовать перегруженную безоперационную версию print (void) ?

А-а-а.Шаблоны функций и перегрузка с легкостью справятся с этим во время выполнения.

Будет несколько сложнее, если вы захотите обработать это во время компиляции, для использования с макросами #if или static-compile-time-asserts.

Но так как вы хотите только первое, могу ли я предложить что-то вроде этого в качестве отправной точки:

(протестировано в (GCC) 3.4.4 и 4.0.1. - Я знаю, мне нужнообновить!)

#include <iostream>
using namespace std;

struct Foo {
  void operator()() {}
};
struct Bar {
  bool operator()() { return false; }
};
Foo foo;
Bar bar;
bool baz() { return false; }
void bang() {}


struct IsVoid
{
  typedef char YES[1];
  typedef char NO[2];

        /* Testing functions for void return value. */

  template <typename T>
  static IsVoid::NO  & testFunction( T (*f)() );

  static IsVoid::YES & testFunction( void (*f)() );

  static IsVoid::NO  & testFunction( ... );

        /* Testing Objects for "void operator()()" void return value. */

  template <typename C, void (C::*)()>
  struct hasOperatorMethodStruct { };

  template <typename C>
  static YES & testMethod( hasOperatorMethodStruct<C, &C::operator()> * );

  template <typename C>
  static NO & testMethod( ... );


        /* Function object method to call to perform test. */
  template <typename T>
  bool operator() (T & t)
  {
    return (    ( sizeof(IsVoid::testFunction(t))  == sizeof(IsVoid::YES) )
             || ( sizeof(IsVoid::testMethod<T>(0)) == sizeof(IsVoid::YES) ) );
  }
};


#define BOUT(X) cout << # X " = " << boolToString(X) << endl;

const char * boolToString( int theBool )
{
  switch ( theBool )
  {
    case true:   return "true";
    case false:  return "false";
    default:     return "unknownvalue";
  }
}

int main()
{
  IsVoid i;

  BOUT( IsVoid()(foo) );
  BOUT( IsVoid()(bar) );
  BOUT( IsVoid()(baz) );
  BOUT( IsVoid()(bang) );
  cout << endl;

  BOUT( i(foo) );
  BOUT( i(bar) );
  BOUT( i(baz) );
  BOUT( i(bang) );
}



Хорошо, я начинаю видеть больше проблемы.

Хотя мы можем сделать что-то вроде этого:

#include <iostream>
using namespace std;

struct FooA {
  void operator()() {}
};
struct FooB {
  bool operator()() { return false; }
};
struct FooC {
  int operator()() { return 17; }
};
struct FooD {
  double operator()() { return 3.14159; }
};
FooA fooA;
FooB fooB;
FooC fooC;
FooD fooD;

void   barA() {}
bool   barB() { return false; }
int    barC() { return 17; }
double barD() { return 3.14159; }


namespace N
{
        /* Functions */

  template <typename R>
  R    run( R (*f)() )    { return (*f)(); }

  bool run( void (*f)() ) { (*f)();  return true; }


        /* Methods */

  template <typename T, typename R>
  R    run( T & t, R (T::*f)() ) { return (t .* f) (); }

  template <typename T>
  bool run( T & t, void (T::*f)() ) { (t .* f) (); return true; }
};


#define SHOW(X) cout << # X " = " << (X) << endl;
#define BOUT(X) cout << # X " = " << boolToString(X) << endl;

const char * boolToString( int theBool )
{
  switch ( theBool )
  {
    case true:   return "true";
    case false:  return "false";
    default:     return "unknownvalue";
  }
}


int main()
{
  SHOW( N::run( barA ) );
  BOUT( N::run( barA ) );
  SHOW( N::run( barB ) );
  BOUT( N::run( barB ) );
  SHOW( N::run( barC ) );
  SHOW( N::run( barD ) );
  cout << endl;

  SHOW( N::run(fooA,&FooA::operator()));
  BOUT( N::run(fooA,&FooA::operator()));
  SHOW( N::run(fooB,&FooB::operator()));
  BOUT( N::run(fooB,&FooB::operator()));
  SHOW( N::run(fooC,&FooC::operator()));
  SHOW( N::run(fooD,&FooD::operator()));
}

У вас все еще есть эта неприятная потребность кормить & CLASS :: operator () в качестве аргумента.


В конечном итогев то время как мы можем определить, возвращает ли метод объекта operator () пустоту, мы обычно не можем перегрузить, основываясь на возвращаемых типах.

Мы можем обойти это ограничение перегрузки с помощью специализации шаблона.Но затем мы попадаем в это уродство, где нам все еще нужно указывать типы ... Особенно тип возврата!Либо вручную, либо путем передачи подходящего аргумента, из которого мы можем извлечь необходимые типы.

Кстати: #define макросы тоже не помогут.Инструменты типа?: Требуют одинакового типа для обоих?и: часть.

Так что это лучшее, что я могу сделать ...



Конечно ...

Если вам не нужнотип возврата ...

Если вы просто передаете результат другой функции ...

Вы можете сделать что-то вроде этого:

#include <iostream>
using namespace std;

struct FooA {
  void operator()() {}
};
struct FooB {
  bool operator()() { return false; }
};
struct FooC {
  int operator()() { return 17; }
};
struct FooD {
  double operator()() { return 3.14159; }
};
FooA fooA;
FooB fooB;
FooC fooC;
FooD fooD;

void   barA() {}
bool   barB() { return false; }
int    barC() { return 17; }
double barD() { return 3.14159; }


#define SHOW(X) cout << # X " = " << (X) << endl;

namespace N
{
  template <typename T, typename R>
  R    run( T & t, R (T::*f)() ) { return (t .* f) (); }

  template <typename T>
  bool run( T & t, void (T::*f)() ) { (t .* f) (); return true; }


  template <typename T>
  void R( T & t )
  {
    SHOW( N::run( t, &T::operator() ) );
  }

  template <typename T>
  void R( T (*f)() )
  {
    SHOW( (*f)() );
  }

  void R( void (*f)() )
  {
    (*f)();
    SHOW( true );
  }
};


int main()
{
  N::R( barA );
  N::R( barB );
  N::R( barC );
  N::R( barD );
  N::R( fooA );
  N::R( fooB );
  N::R( fooC );
  N::R( fooD );
}
1 голос
/ 27 декабря 2010

Возможно, вы можете использовать тот факт, что void & не имеет смысла как тип, но void * делает.

0 голосов
/ 27 декабря 2010

Если вы можете использовать Boost, вероятно, будет использоваться следующий код.Я предполагаю, что все функции / функторы являются нулевыми, как в вашем вопросе.Однако, чтобы использовать это, result_type должен быть определен во всех функторах (класс функции).

#include <boost/utility/result_of.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>
using namespace boost; // Sorry, for brevity

template< class F >
// typename result_of< F() >::type
typename disable_if<
  is_void< typename result_of< F() >::type >
, typename result_of< F() >::type
>::type
f( F const& x )
{
  return x();
}

template< class F >
typename enable_if<
  is_void< typename result_of< F() >::type >, bool
>::type
f( F const& x )
{
  x();
  return true;
}

template< class T >
T f( T x() )
{
  return x();
}

bool f( void x() )
{
  x();
  return true;
}

static void void_f() {}
static int int_f() { return 1; }

struct V {
  typedef void result_type;
  result_type operator()() const {}
};

struct A {
  typedef int result_type;
  result_type operator()() const { return 1; }
};

int main()
{
  A  a;
  V  v;
  f( void_f );
  f( int_f );
  f( a );
  f( v );
}

Надеюсь, это поможет

0 голосов
/ 27 декабря 2010

С C ++ 0x вы можете легко это сделать, используя decltype.

0 голосов
/ 27 декабря 2010

попробуйте специализироваться для типа возврата void:

template<class F>
class traits;

template<class F, class T>
class traits<T (F)()>;

template<class F>
class traits<void (F)()>;

я думаю ...

...