4

Suppose I have this struct

struct MyStruct {

  static MyStruct Create(int x) {
    return { x*2, x>3 };
  }

  MyStruct(const MyStruct& c) = delete;  // no copy c'tor

private:
  MyStruct(int a_, bool b_) : a(a_), b(b_) {}  // private c'tor -- can't use new

  const int a;
  const bool b;
};

Edit: I deleted the copy constructor. This is simplified example of some classes I have in my codebase where they don't have copy c'tors.

I can get an instance on the stack like so:

int main() {
  auto foo = MyStruct::Create(2);
  return 0;
}

But suppose I need a pointer instead (or unique_ptr is fine), and I can't change the implementation of MyStruct, how can I do that?

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Does this answer your question? [C++ How to create a std::unique\_ptr from a class that takes parameters on constructor](https://stackoverflow.com/questions/31173299/c-how-to-create-a-stdunique-ptr-from-a-class-that-takes-parameters-on-constr) – ShadowMitia Oct 07 '21 at 20:17
  • Are you okay with making a copy in order to get it into dynamic storage? – NathanOliver Oct 07 '21 at 20:18
  • @NathanOliver Would prefer to avoid a copy. And is that even possible without a copy c'tor? – mpen Oct 07 '21 at 20:19
  • @ShadowMitia No it does not. I phrased my question this way because `Create()` is not a constructor. I can't use `std::make_unique` – mpen Oct 07 '21 at 20:21
  • "_Suppose I have this struct_" - Which compiler are you using? – Ted Lyngmo Oct 07 '21 at 20:24
  • 1
    @mpen Well right now the compiler is going to generate a copy and move constructor for you. AFAIK, the only way to do this is to call `create`, and either move/copy that object into dynamic storage like `auto * ptr = new MyStruct(MyStruct::Create(2));`. That said, as is your code can't compile as `MyStruct` is not an aggregate so you can't use aggregate initialization. – NathanOliver Oct 07 '21 at 20:24
  • @NathanOliver It does seem to compile though? https://ideone.com/XBzKbZ – mpen Oct 07 '21 at 20:29
  • @TedLyngmo clang – mpen Oct 07 '21 at 20:29
  • Are you gone use foo outside the scope? – Selvin Oct 07 '21 at 20:30
  • @Selvin In this example no. But I've encountered many scenarios for which I need a pointer instead – mpen Oct 07 '21 at 20:32
  • @NathanOliver `new MyStruct(MyStruct::Create(2))` does seem to work here. Will that be copy-elided -- i.e. created right onto the heap, or created and then copied? Also, is there a solution for when the copy c'tor is explicitly deleted? – mpen Oct 07 '21 at 20:33
  • @mpen That site is using GCC, which by default has an extension on that allows C style designated initializers. If you use `-pedantic` to turn that off and force conformance to the C++ standard, [it does not compile](http://coliru.stacked-crooked.com/a/dac76ec438ab1b58) – NathanOliver Oct 07 '21 at 20:36
  • @mpen I would turn on some compiler flags: [`clang++ -std=c++20 -Wall -Wextra -Werror -pedantic -pedantic-errors`](https://godbolt.org/z/7cTos3Eb9) – Ted Lyngmo Oct 07 '21 at 20:36
  • if you don't want copy, you can move it: new MyStruct(std::move(MyStruct::Create(2))); – Sandro Oct 07 '21 at 20:37
  • I removed my last comment. I'm not sure now if the elision would be allowed or not. I'm more leaning towards that it should be elided. – NathanOliver Oct 07 '21 at 20:51
  • @mpen: So, you're ok with moving, but not copying? Anyway, see my updated answer. – einpoklum Oct 07 '21 at 20:55
  • @einpoklum I mean I'm OK with anything that compiles at this point. The biggest problem with copying is if there's no copy c'tor. But the more performant the better. – mpen Oct 07 '21 at 21:32
  • @mpen I was asking because if you are not going to use `foo` outside the scope then `&foo` should be pretty valid pointer so you can use `{ auto foo = MyStruct::Create(2); auto fooptr = &foo; func_which_use_pointer_only_when_its_running(fooptr); }` – Selvin Oct 08 '21 at 10:04
  • @Selvin Oh..ya. I know about that, but not what I was looking for here :-) – mpen Oct 08 '21 at 21:25

5 Answers5

4

You could wrap MyStruct in another class, which has a MyStruct member. Here's a minimal version of that:

class Wrapper {
public:
    MyStruct ms;
    Wrapper(int x) : ms(MyStruct::Create(x)) { }
};

which you can use like so:

int main() {
  MyStruct::Create(2);
  std::make_unique<Wrapper>(2);
}

This code will not trigger any copies nor moves - because of copy elision (see: What are copy elision and return value optimization?).

You can then add any other constructors and methods you like to such a wrapper, possibly forwarding some of the method calls to the ms member. Some might choose to make ms protected or private.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    Hang on, how can `std::make_unique(2)` work? c'tor is private – mpen Oct 07 '21 at 20:35
  • 1
    @Selvin: Yes, but what OP gave us wasn't the actual implementation. Obviously he created a MWE example for us, except is wasn't working, so I fixed it up a bit. – einpoklum Oct 07 '21 at 20:36
  • @mpen: The ctor of `C` is public; and there's elision, or at worst a move, when constructing the `ms` member. – einpoklum Oct 07 '21 at 20:37
  • `MyStruct() = default;` shouldn't work with two uninitialized `const` members. – Ted Lyngmo Oct 07 '21 at 20:38
  • @TedLyngmo: Shouldn't it? The members are default-initializable. Also, it does seem to work... see the GodBolt link. – einpoklum Oct 07 '21 at 20:47
  • @TedLyngmo I was hoping that would let me use an initializer list while making the c'tor private. But no matter, I fixed up the example. – mpen Oct 07 '21 at 20:48
  • @einpoklum What I mean is that the defaulted default constructor becomes deleted if you have `const` members that you don't initialize. Afaik, it isn't the same as `MyStruct() : a{}, b{} {}` (which would be a valid default constructor) – Ted Lyngmo Oct 07 '21 at 20:51
  • 1
    @TedLyngmo: I've been bitten by const members before, so I guess what you're saying could be possible... except that GCC 11 and clang 13 accept it. Maybe the language of the standard has changed recently about such members? – einpoklum Oct 07 '21 at 20:56
  • @einpoklum Hmm, I seem to get the warning with [clang++](https://godbolt.org/z/WPf1zaYn8) even without any options. – Ted Lyngmo Oct 07 '21 at 20:59
2

Is this what you're looking for?

auto baz  = std::make_unique<MyStruct>( MyStruct::Create(2) );  // unique pointer
einpoklum
  • 118,144
  • 57
  • 340
  • 684
Jojo
  • 21
  • 5
  • This costs a bit more than my solution (i.e. it costs you a move), but that might be optimized away if you're lucky. Plus it's very succinct. So definitely +1. – einpoklum Oct 07 '21 at 20:45
  • @einpoklum This answer is not right: this will eventually resolve to the deleted copy constructor of `MyStruct`, and injecting this proposed solution into your program makes it ill-formed. – dfrib Oct 07 '21 at 21:23
  • No, won't compile because the copy c'tor is deleted (sorry, I should have specified that earlier) – mpen Oct 07 '21 at 21:24
1

A comment rather than an answer, to avoid confusion for future readers.

I can get an instance on the stack like so:

int main() {
  auto foo = MyStruct::Create(2);
  return 0;
}

Note that this is only true as of C++17 and guaranteed copy elision, whereas the program is ill-formed is C++14, as even if the copy may be elided, the initialization of foo is copy-initialization from a temporary (in C++17: the temporary is never materialized).

dfrib
  • 70,367
  • 12
  • 127
  • 192
0

One more way to do it:

  struct ChildStruct : public MyStruct {                                                                                  
      ChildStruct(int x) : MyStruct(MyStruct::Create(x))                             
      {}                                                                             
                                                                                     
  };                                                                                 
                                                                                     
  int main() {                                                                       
    MyStruct *foo1 = new ChildStruct(2);                                             
    return 0;                                                                        
  }  
Sandro
  • 2,707
  • 2
  • 20
  • 30
  • 2
    After OP's edit, this won't work, because you're trying to use the move (or copy) constructor of MyStruct. But I would +1 this as an answer to the original question. – einpoklum Oct 07 '21 at 20:53
0

C style solution. I am not sure that this is not UB, but for simple struct with 2 integer fields it should work.

int main() {                                                                    
    auto foo = MyStruct::Create(2);                                               
                                                                                  
    MyStruct *p = (MyStruct*)malloc(sizeof(MyStruct));                            
    memcpy(p, &foo, sizeof(MyStruct));                                            
    //...
    free(p);
    return 0;                                                                     
}
Sandro
  • 2,707
  • 2
  • 20
  • 30
  • This is unspecified behavior since `MyStruct` is not trivially copyable. – NathanOliver Oct 07 '21 at 21:18
  • Actually copy constructor was explicitly deleted, but struct contains only 2 trivially copyable members, so... in practice it should work... – Sandro Oct 07 '21 at 21:23