NoSenseEtAl
NoSenseEtAl

Reputation: 30028

Best way to use constructor as a function in std::transform?

I tried to specify just the constructor as the last argument of transform but it did not work since in C++ you can not take address of a constructor, but also signature would not match, constructor does not have return value.

I can easily wrap it in a lambda, but I wonder is there a simpler way to do this? constructor functor does not look too bad, but it is not in std::, I would prefer some standard way of doing this(I looked into std::allocator<T>::construct, but signature does not match).

Here are my attempts:

    #include <iostream>
    #include <utility>
    #include <iterator>
    #include <algorithm>
    #include <vector>
    
    struct Square{
        explicit Square(const int a):a(a){}
        int a;
    };
    
    
    template<typename T>
    struct constructor{
    template<typename... Args>
    T operator()(Args&&... args) {
        return T(std::forward<Args>(args)...);
    }
    };
    
    int main() {
      std::vector<int> ints{1,2};
      std::vector<Square> squares;
      // :( can not take address of a constructor
      //std::transform(ints.begin(), ints.end(), std::back_inserter(squares), &Square::Square);
      // :/ works, but requires manual implementation of constructor
      std::transform(ints.begin(), ints.end(), std::back_inserter(squares), constructor<Square>{});
      // :/ works, but spammy
      // std::transform(ints.begin(), ints.end(), std::back_inserter(squares), [](const int i) {return Square(i);});
    
      for (const auto square:squares){
          std::cout << square.a << std::endl;
      }
    }

Upvotes: 5

Views: 1309

Answers (1)

metalfox
metalfox

Reputation: 6731

Limiting ourselves to standard C++20 facilities, I would suggest simply using the vector constructor taking iterators (which might require a tweak in the constructor of Square as stated below):

#include <iostream>
#include <utility>
#include <iterator>
#include <algorithm>
#include <vector>

struct Square{
    explicit Square(const int a):a(a){}
    int a;
};

int main() {
  std::vector<int> ints{1,2};
  std::vector<Square> squares(ints.begin(), ints.end());

  for (const auto square:squares){
      std::cout << square.a << std::endl;
  }
}

This is accepted by GCC/libstdc++ but I’m not 100% sure it should because the standard seems to imply that the elements referred to by the iterators must implicitly convertible to the vector value_type (see this answer for more details), and your constructor is explicit.

Future revisions of the standard are expected to gain ranges::to. The current proposal P1206R2 has an example suggesting that the types of the input range elements must be ConvertibleTo the type of the elements in the container, and that concept isn’t satisfied if you only have an explicit constructor.

In fact, the range-v3 implementation of ranges::to requires the constructor to be a converting constructor for your example to work:

#include <iostream>
#include <utility>
#include <iterator>
#include <algorithm>
#include <vector>
#include <range/v3/all.hpp>

struct Square{
    /*explicit*/ Square(const int a):a(a){}
    int a;
};

int main() {
  std::vector<int> ints{1,2};
  std::vector<Square> squares = ints | ranges::to<std::vector<Square>>();

  for (const auto square:squares){
      std::cout << square.a << std::endl;
  }
}

Upvotes: 3

Related Questions