user5406764
user5406764

Reputation: 1795

C++14 tuple type indexing fails on inferred type from std::bind that I want to be std::function

I am getting an error when using template type deduction combined with C++14 std::get<> with type indices. The code might look a little complex, but I've tried to whittle it down to the bare basics of what's going on. It's really just an Observer pattern... The struct 'A' allows observers to be set based on message type (M1, M2, ...). Note that there is only one observer per message type, in order to keep things simple.

Now the trick (and the part that is failing) is using C++14's std::get<>, which allows you to index into a tuple of unique types using the actual type. Here is a simple example demonstrating what I mean:

void sample()
{
    std::tuple<int, float> myTuple;
    std::get<float>(myTuple) = 3.141f;  // C++14 allows this
    std::get<1>(myTuple) = 3.141f;      // C++11 way to do it, using index
}

With this in mind, here is my program (separate from the above code) that does NOT compile because the C++14 tuple type indexing is failing on an inferred type:

#include <cxxabi.h>
#include <stdlib.h>
#include <functional>
#include <vector>
#include <tuple>
#include <typeinfo>
#include <iostream>
#include <string>

// ===================================
// A quick'n'dirty way to print types (nonportable)
// And yes, I know this code could be improved :)
inline
std::string demangle(char const *mangled)
{
   char *output = (char *)malloc(16384);
   size_t length = 16384;
   int status;

   __cxxabiv1::__cxa_demangle(mangled, output, &length, &status);
   std::string s(output, length);
   free(output);
   return s;
}

#define DEMANGLE(T) demangle(typeid(T).name())

// ===================================
struct A
{
   struct M1
   {};

   struct M2
   {};

   using Tuple = std::tuple<
        std::function<void(M1 const &)>
       ,std::function<void(M2 const &)>
   >;

   template<typename T>
   void setObserver(T func)
   {
      // This works fine
      std::cout << DEMANGLE(T) << std::endl;

      // ************************************************
      // The line below does not compile (std::get fails)
      //
      // Note the type of T prints out as:
      // std::_Bind<std::_Mem_fn<void (B::*)(A::M1 const&)> (B*, std::_Placeholder<1>)>
      //
      // Rather than the (desired):
      // std::function<void (A::M1 const&)>(A::M1 const&)> (B*, std::_Placeholder<1>)>
      //
      // ************************************************
      std::get<T>(tuple_) = func; // C++14 only
   }

private:
   Tuple tuple_;
};

// ===================================
struct B
{
   void func(A::M1 const &)
   {}
};

// ===================================
int main()
{
   A *a = new A;
   B *b = new B;

   using namespace std::placeholders;

   a->addObserver(std::bind(&B::func, b, _1));

   return 0;
}

UPDATE:

The proposed solution does solve the problem of converting from std::bind(...) to std::function(...), BUT it requires me to have a separate setObserver() function for each of my types M1, M2, ...

How can I templatize setObserver() to fix this?

Upvotes: 1

Views: 139

Answers (1)

PiotrNycz
PiotrNycz

Reputation: 24412

std::bind()does not return std::functionbut some unspecified type convertible to std::function<>. Secondly, std::get<>() in C++14 form requires exact type, not just anything convertible to one of tuples member types.

To achieve what you want - you need to convert your T to one of your tuple types.

E.g. you might move your current setObserver to private section and rename it - and create functions for your desired types:

   template<typename T>
   auto setObserver(T func) 
      -> typename std::enable_if<std::is_convertible<T, std::function<void(M1 const &)>>::value>::type 
   {
       this->template setObserverImpl<std::function<void(M1 const &)>>(func);
   }
   template<typename T>
   auto setObserver(T func) 
      -> typename std::enable_if<std::is_convertible<T, std::function<void(M2 const &)>>::value>::type 
   {
       this->template setObserverImpl<std::function<void(M2 const &)>>(func);
   }

private: 
   template<typename T>
   void setObserverImpl(T func)
   {
     // no change here
   }

You need to make the current setObserver to be template function unless you have just two types.

To template this solution - either use variant functions for each Mx type where "non convertible version" will be empty:

    template <typename T, typename M>
    auto setObserverTemplate(T func) -> typename std::enable_if<std::is_convertible<T, std::function<void(M const &)>>::value>::type 
    {
       this->template setObserverImpl<std::function<void(M const &)>>(func);  
    }
    template <typename T, typename M>
    auto setObserverTemplate(T) -> typename std::enable_if<not std::is_convertible<T, std::function<void(M const &)>>::value>::type 
    {
       // do nothing
    }

   template<typename T>
   auto setObserver(T func)
   {
       this->template setObserverTemplate<T,M1>(func);
       this->template setObserverTemplate<T,M2>(func);
   }

Or, probably much better - variadic version with sentinel empty function:

   template<typename T, typename MFirst, typename ...M>
   auto setObserverVariadic(T func)
   {
       this->template setObserverTemplate<T,MFirst>(func);
       this->template setObserverVariadic<T,M...>(func);
   }
   template<typename T>
   auto setObserverVariadic(T)
   {
   }

   template<typename T>
   auto setObserver(T func)
   {
       this->template setObserverVariadic<T,M1,M2>(func);
   }

Last comment - you might try to "retrieve" current types (I mean these list of Mx types) from your tuple type. How to do it - that could be good candidate for new question.

Upvotes: 3

Related Questions