пятница, 25 июля 2014 г.

[prog.c++] Досадный баг в MSVC++ 2013

Современный C++ позволяет писать довольно лаконично. Например, если есть вот такая структура с функциями-фабриками:

struct factories_t
{
   std::function< dispatcher_unique_ptr_t() > m_disp_factory;
   std::function< binder_unique_ptr_t() > m_bind_factory;
};

То можно написать вот такую функцию, которая создает экземпляры данной структуры:

factories_t
create_factories( const std::string & name )
{
   if"first" == name )
      return {
         []() { return create_first_dispatcher(); },
         []() { return create_first_binder(); }
      };

   return {
      []() { return create_second_dispatcher(); },
      []() { return create_second_binder(); }
   };
}

К сожалению, в MSVC++ 2013 подобный код работает только, если написанные в create_factories() лямбды не требуют захвата каких-либо переменных. Если же требуют, т.е. если написать лямбду вот так:

return {
   []() { return create_first_dispatcher(); },
   [name]() { return create_first_binder(); }
};

То при попытке обратиться к ней возникнет исключение с текстом "bad function call" (т.е., похоже, в этом случае лямбда не преобразуется корректно в объект std::function).

Между тем под GCC (4.8.3 и 4.9.0) нормально работают и вариант без захвата контекста, и вариант с захватом контекста.

Пичалька...

Под катом полный самодостаточный код теста для самостоятельной проверки.

#include <functional>
#include <iostream>
#include <string>
#include <memory>

//
// Basic types
//
class dispatcher_t
{
public :
   virtual ~dispatcher_t()
   {}

   virtual void
   say() = 0;
};

typedef std::unique_ptr< dispatcher_t > dispatcher_unique_ptr_t;

class binder_t
{
public :
   virtual ~binder_t()
   {}

   virtual void
   say() = 0;
};

typedef std::unique_ptr< binder_t > binder_unique_ptr_t;

//
// Basic types implementations.
//

class first_dispatcher_t : public dispatcher_t
{
public :
   virtual void
   say() override
   {
      std::cout << "first dispatcher" << std::endl;
   }
};

class first_binder_t : public binder_t
{
public :
   virtual void
   say() override
   {
      std::cout << "first binder" << std::endl;
   }
};

dispatcher_unique_ptr_t
create_first_dispatcher()
{
   return dispatcher_unique_ptr_t( new first_dispatcher_t() );
}

binder_unique_ptr_t
create_first_binder()
{
   return binder_unique_ptr_t( new first_binder_t() );
}

class second_dispatcher_t : public dispatcher_t
{
public :
   virtual void
   say() override
   {
      std::cout << "second dispatcher" << std::endl;
   }
};

class second_binder_t : public binder_t
{
public :
   virtual void
   say() override
   {
      std::cout << "second binder" << std::endl;
   }
};

dispatcher_unique_ptr_t
create_second_dispatcher()
{
   return dispatcher_unique_ptr_t( new second_dispatcher_t() );
}

binder_unique_ptr_t
create_second_binder()
{
   return binder_unique_ptr_t( new second_binder_t() );
}

//
// Test infrastructure
//

struct factories_t
{
   std::function< dispatcher_unique_ptr_t() > m_disp_factory;
   std::function< binder_unique_ptr_t() > m_bind_factory;
};

void
test( const factories_t & factories )
{
   std::cout << "creating dispatcher..." << std::flush;
   auto d = factories.m_disp_factory();
   std::cout << "ok" << std::endl;
   d->say();

   std::cout << "creating binder..." << std::flush;
   auto b = factories.m_bind_factory();
   std::cout << "ok" << std::endl;
   b->say();
}

factories_t
create_factories( const std::string & name )
{
   if"first" == name )
      return {
         []() { return create_first_dispatcher(); },
         [name]() { return create_first_binder(); }
      };

   return {
      []() { return create_second_dispatcher(); },
      []() { return create_second_binder(); }
   };
}

int main()
{
   try
   {
      test( create_factories( "second" ) );
      test( create_factories( "first" ) );

      return 0;
   }
   catchconst std::exception & x )
   {
      std::cerr << "Exception: " << x.what() << std::endl;
   }

   return 2;
}

Комментариев нет: