Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: boost fusion fold with c++14 generic lambdas

I am trying to pass a generic lambda function to a boost::fusion::fold function so that I can iterate all the elements of a boost::fusion::vector. My goal is to call a non-const member function from each element in the vector. The problem is that even though the vector holds non-const values the type deduced by the generic lambda is a const reference. This results in my gcc-4.9.0 compiler (using CygWin) complaining that I am discarding the const qualifier.

#include <iostream>                                        
#include <boost/fusion/include/vector.hpp>                             
#include <boost/fusion/include/fold.hpp>                               
#include <boost/fusion/include/for_each.hpp>                               

class Silly {                                              

public:                                                
  Silly(int x)                                             
    : x_(x){}                                              

  int increment(int i) {                                       
    return x_ += i;                                        
  }                                                                                                                                        

private:                                               
  int x_;                                              
};                                                 

using my_type = boost::fusion::vector<Silly, Silly>;                           


int main() {                                               
  my_type my_vector(1, 2);                                     
  boost::fusion::fold(my_vector, 0, [](int i, auto& x){return x.increment(i);}); //error: passing 'const Silly' as 'this' argument of 'int Silly::increment(int)' discards qualifiers                                      
}                                                                                              

Now, if instead of the lambda I pass the following functor, the program compiles cleanly

struct functor {

  template <class X>
  int operator()(int i, X& x) {
  return x.increment(i);
 }
};

Is this a boost::fusion bug or am I missing something? Thanks in advance!

like image 453
linuxfever Avatar asked Oct 09 '14 14:10

linuxfever


1 Answers

There are multiple boost::fusion::fold overloads. From boost's svn repo:

template<typename Seq, typename State, typename F>
inline typename result_of::BOOST_FUSION_FOLD_NAME<
    Seq const
  , State const
  , F
>::type
BOOST_FUSION_FOLD_NAME(Seq const& seq, State const& state, F f)
{
    return result_of::BOOST_FUSION_FOLD_NAME<Seq const,State const,F>::call(
        state,
        seq,
        f);
}

template<typename Seq, typename State, typename F>
inline typename result_of::BOOST_FUSION_FOLD_NAME<
    Seq
  , State const
  , F
>::type
BOOST_FUSION_FOLD_NAME(Seq& seq, State& state, F f)
{
    return result_of::BOOST_FUSION_FOLD_NAME<Seq,State,F>::call(
        state,
        seq,
        f);
}

template<typename Seq, typename State, typename F>
inline typename result_of::BOOST_FUSION_FOLD_NAME<
    Seq const
  , State const
  , F
>::type
BOOST_FUSION_FOLD_NAME(Seq const& seq, State& state, F f)
{
    return result_of::BOOST_FUSION_FOLD_NAME<Seq const,State,F>::call(
        state,
        seq,
        f);
}

The compiler is allowed to instantiate the class template result_of::BOOST_FUSION_FOLD_NAME (*) in the return type for all these variants once type deduction and substitution have succeeded, before an overload is selected. In this case, the compiler must instantiate this class template in order to determine whether or not the return type is valid. If substitution (of the template arguments) in the return type leads to an invalid type in the immediate context, the overload is discarded. This is known as SFINAE.

(*) This name typically resolves to result_of::fold.

The instantiation of one of the overloads that has a Seq const& parameter tries now to determine the return type of the lambda. However, instantiating the lambda with a Silly const& second argument fails: increment cannot be called on a const object (this is what the compiler tells you).

If determining the return type fails, this should lead to a substitution failure in the fold overload we're trying to determine the return type of. However, substitution failures due to automatic return type deduction in lambdas and C++14 functions are not in the immediate context of the original template fold: They happen within the function that uses automatic return type deduction (here: the lambda).

A substitution failure not in the immediate context of the original template is a hard error, it is not a SFINAE-type error that you could recover from. (SFINAE = SFIICINAE)

If you explicitly specify the return type of the lambda, [](int i, auto& x) -> int {return x.increment(i);}, the function/lambda does not need to be instantiated to determine the return type. It can be determined from the declaration alone. Therefore, no substitution failure based on the return type happens for any of the overloads, and usual overload resolution can select an appropriate overload. The non-const Seq& overload is chosen, and the instantiation of the lambda will be valid.

Similarly, for the explicitly written functor: If the return type can be determined without instantiating the function, no error will occur. If you use C++14's return type deduction for ordinary functions, the same problem occurs:

struct functor {
  template <class X>
  auto operator()(int i, X& x) {
  return x.increment(i);
 }
};

As a side remark: As T.C. noted in a comment, a hard error also occurs for the following function object type:

struct functor {
  int operator()(int i, Silly& x) {
  return x.increment(i);
 }
};

The reason this fails is different, though: Again, all fold overloads need to instantiate the result_of::fold class template with their respective types. This class template however does not produce substitution errors in the immediate context: If the function passed cannot be called with the argument types passed, a hard error will occur.

Since a function of the type int(int, Silly&) cannot be called with arguments of the type int and Silly const&, a hard error occurs.

When writing the apply operator as a template (as in the example with C++14 return type deduction), the declaration of the operator() template can be instantiated for an second argument of type Silly const& (X will be deduced to be Silly const). The function definition cannot be instantiated, though, as this will result in the same error as in the OP: Silly::increment requires a non-const Silly object.

The instantiation of the definition of the function however happens only after overload resolution if there is no return type deduction. Therefore, this will not produce substitution failures.

like image 174
dyp Avatar answered Sep 30 '22 00:09

dyp