1

I am trying to implement an "emplace" feature in an object. It is structured like below. I have a template object that pairs a size_t to a template type. I would like to be able to construct this in place in a std library container, e.g., a vector. I am not using a std::pair as my class B will be offering other functions around the data.

How do I need to modify the code, so that I can invoke emplace like in the main?

#include <iostream>                                                                 
#include <vector>                                                                   
using namespace std;                                                                

class C {                                                                           
  public:                                                                           
    C(const string& _s) : s(_s) {}                                                  
    C(string&& _s) : s(_s) {}                                                       

  private:                                                                          
    string s;                                                                       
};                                                                                  

template<typename A>                                                                
class B {                                                                           
  public:                                                                           

    B(size_t _i, const A& _a) : i(_i), a(_a) {}                                     
    B(size_t _i, A&& _a) : i(_i), a(_a) {}                                                  

  private:                                                                          
    size_t i;                                                                       
    A a;                                                                            
};                                                                                  

int main() {                                                                        
  vector<B<C>> v;                                                                   
  v.emplace_back(5, "Hello");                                                       
}
pippin1289
  • 4,861
  • 2
  • 22
  • 37
  • `v.emplace_back(5, C("Hello"))` – David G Dec 18 '17 at 20:38
  • @0x499602D2 Wouldn't this invoke an additional copy of C into the vector? I am looking to avoid this, and provide a nice looking syntax to my class – pippin1289 Dec 18 '17 at 20:39
  • The string `"Hello"` needs to be converted to a `C`. That can't happen without creating a temporary `C("Hello")` object. A copy will not happen since it's bound to the rvalue reference `_a`. The actual copy occurs with `a(_a)` which happens even if a temporary wasn't made. – David G Dec 18 '17 at 20:45
  • @0x499602D2 Ah I understand. As for pursuit of the desired syntax, any possible work around there? – pippin1289 Dec 18 '17 at 20:47
  • 1
    v.emplace_back(5, std::string{"Hello"}) ? (maybe with some `std::move()` in move-constructors?) – max66 Dec 18 '17 at 20:48
  • @max66 This fixed it for me. – pippin1289 Dec 18 '17 at 20:53
  • Add a constructor of `B` that can directly forward arguments to the constructor for the `a` element? e.g. `template explicit B(size_t _i, T&&... a_constructor_args) : i(_i), a(std::forward(a_constructor_args)...) {}` – Daniel Schepler Dec 18 '17 at 23:35

2 Answers2

3

You are allowed one user-defined conversion sequence per argument and you have two:

  1. const char* --> std::string
  2. std::string --> C

What you can do is reduce the number of implicit conversions by implementing a generic forwarding constructor:

template<typename A>                                                                
class B {                                                                           
  public:

    template<typename X>
    B(size_t _i, X&& _a) : i(_i), a(std::forward<X>(_a)) {}                                                  

  ...
};

That will generate a constructor B(size_t, const char*) which will match emplace_back(5, "Hello") perfectly, with just a single conversion to construct a from const char *.

rustyx
  • 80,671
  • 25
  • 200
  • 267
3

The reason this doesn't work is not because you can't have implicit conversions. In the following code:

vector<B<C>> v;                                                                   
v.emplace_back(5, "Hello");

emplace_back does perfect forwarding to the constructor of the type contained by v, which is B<C>. So we end up with a call to (it actually happens via the allocator, but let's ignore the details):

B<C>(5, "Hello")

Why doesn't this work? You often hear people say that you can't get implicit conversions when calling templates; this is loosely true. But B<C>'s constructor is not a template; B is a template class which has already been templated out, so B<C> is a normal class and whose constructor is not a template. So implicit conversions are allowed.

However, implicit conversions in C++ consist of:

  1. zero or one standard conversion sequence;
  2. zero or one user-defined conversion;
  3. zero or one standard conversion sequence.

(http://en.cppreference.com/w/cpp/language/implicit_conversion).

A user-defined conversion is basically a class defining an implicit constructor, or conversion operator. string may be part of the standard library, but it's just a normal class, and the conversion from const char* to string already counts as the user conversion. So you can't get a second one from string to C.

One way to solve this issue is to template your constructor for C, but single argument implicit template constructors open you to all kinds of other issues. I'd probably just define a second constructor that takes a const char*. There was a whole talk on pretty well exactly this issue, at cppcon, btw: https://www.youtube.com/watch?v=PNRju6_yn3o.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72