std::any for objects that can't be copy constructed
Asked Answered
E

1

15

I have an object which holds a unique_ptr and as such can't be copy constructed without doing a deep copy (which I don't want).

I'd like to have std::any hold that object, but the only alternatives I've found is to either make std::any hold a pointer, which adds a useless indirection, or make my object have a unique ptr. The code bellow will hopefully illustrate my point:

//Compiled with clang++ -std=c++2a; clang version 5.0.0
#include <iostream>
#include <any>
#include <memory>

struct A {
        std::unique_ptr<int> m = std::make_unique<int>(11);
        A(A&& a): m(std::move(a.m)) {}
        A() {}
};

struct B {
        std::shared_ptr<int> m = std::make_shared<int>(11);
};


template<class T>
void use_any_ptr() {
        std::any a{new T{}};
        std::cout << *(std::any_cast<T*>(a)->m) << std::endl;
}

template<class T>
void use_any() {
        std::any a{T{}};
        std::cout << *(std::any_cast<T>(a).m) << std::endl;
}


int main() {
        use_any_ptr<A>(); // Workaround using a pointer
        use_any<B>(); // Workaround using shared pointer
        use_any<A>(); // Breaks because A has no cc no matching constructor for initialization of 'std::any'
}

As far as I understand the construction of std::any seems to require copying of the object, however I'm unsure why the object couldn't simply be moved.

Is there a way to work around this ? That is, other than using a shared_ptr, which means I'm basically expressing the "wrong thing" for the sake of creating the any object or passing std::any a pointer (which is an unneeded level of indirection, since std::any holds a void pointer to the type to being with as far as I can tell).

Is there a different implementation of any that could use the move ctr at creation instead of the copy ctr ?

Or am I being silly and not understanding the "real" problem here ?

Equipoise answered 20/10, 2017 at 15:14 Comment(0)
K
5

Or am I being silly and not understanding the "real" problem here ?

The problem is simple here: std::any has a copy constructor and thus it requires the object contained is copy constructible.

The way you can work around it is by defining a custom object that contains the pointer, that is copyable and that implements a proper logic for it (whatever proper means in your case, I can't see how you could copy an std::any and thus an object contained there that owns an std::unique_ptr, but maybe you can get away with it somehow in your case).

Otherwise rethink your design and try to get rid of the need of an std::any. It hasn't much uses indeed and probably you can get the same with a bit of template machinery or an std::variant or whatever

Kr answered 20/10, 2017 at 16:29 Comment(11)
Ok, well, in that case I misunderstood the issue. I thought std::any's construction required a copy. I assume std::any would work without copy construction though, right ? So should I just re-write or use an impl without the copy ctr ? Or is there a way to remove the copy_ctr off a class ?Equipoise
@Equipoise It requires the type you use to construct it is copy constructible. It checks it using traits (namely std::is_copy_constructible).Kr
@Equipoise No way to remove the requirement from std::any. You can either implement your any or rethink your design. I would go for the second actually.Kr
Yes, I get that. But if I never use the copy ctr of std::any I could in theory use it without needing the copy ctr (and implicitly not need the cctr of the object held inside).Equipoise
@Equipoise C++ doesn't work this way. The compiler cannot know you are not going to use the copy constructor in another compilation unit.Kr
I'd rather not implement std::any myself, however I need a vector of objects that can hold "any" type and I can't find a work around. One variant would be to use std::tuple but since the ordering of the types is not always the same and the size can vary using std::tuple becomes a pain because it's not made to be accessed with a runtime indexEquipoise
@George: any is not a template on the type it takes. So any statically has a copy constructor. It doesn't matter if you call it or not, it must exist. And therefore it must have an implementation. And that implementation must be able to work with any type that can be stored within it. Therefore, any must statically require its contents to have a copy constructor.Azzieb
@Equipoise look for type erasure to accomplish that.Kr
@NicolBolas Indeed clang uses std::is_copy_constructible on construction to check the type involved (well, I'm testing it on mobile, I could be wrong, but it seems it's doing that).Kr
It may be worth elaborating NicolBolas' comment in the answer, since it is quite important. This sentence alone: "The problem is simple here: std::any has a copy constructor and thus it requires the object contained is copy-constructible." is not much useful by itself. std::vector also has a copy constructor and the contained objects do not need to be copy-constructible.Jefe
@DanielLangr well, technically speaking, you can implement something similar to std::any that also works for non-copyable types. As long as you can correctly define a policy for this case, you can use std::is_copy_constructible and the like. The actual problem is: what's a proper policy for this case? A general purpose one doesn't really exist, so dropping the non-copyable types support seems reasonable to me for an implementation that belongs to the standard library.Kr

© 2022 - 2024 — McMap. All rights reserved.