20

I have an external library which I can not modify. The library declares a template function that for some reason returns const non-reference object:

template<class C>
const C foo();

I have another external library which I can not modify too. The library declares a class that is non-copyable and has a move constructor from a non-const object only:

struct bar {
    bar();
    bar(const bar&)=delete;
    bar(bar&&);
};

Now I need to use foo<bar>. A simple usage:

bar buz() {
    return foo<bar>();
}

fails with

main.cpp: In function 'bar buz()':
main.cpp:13:21: error: use of deleted function 'bar::bar(const bar&)'
     return foo<bar>();
                     ^
main.cpp:8:5: note: declared here
     bar(const bar&)=delete;
     ^~~

which makes sense, and no simple workaround make the code compile.

However, if I add some more complex workaround:

bar buz() {
    return const_cast<bar&&>(std::move(foo<bar>()));
}

it compiles and the whole code work as expected (not only the simplified example above, but my real code too).

However, it is safe, or am I running into some undefined behavior? Is there any better workaround?


I have read and I understand questions about returning const from functions (1, 2), and the common answer there seems to be that returning const objects is discouraged in modern C++, but my question is not about it, but about how can I workaround the situation when an external library returns const object.

Community
  • 1
  • 1
Petr
  • 9,812
  • 1
  • 28
  • 52
  • The implicit assumption in the question is that the API is in the wrong here. What is the library you're using? What problem are you trying to solve that requires you to work around the API? – uh oh somebody needs a pupper May 24 '16 at 16:04
  • `const_cast(foo())` would not be enough? it still leaves unnamed object in return statement, so move-semantics should kick in – Hcorg May 24 '16 at 16:04
  • @Hcorg, `error: invalid use of const_cast with type 'bar', which is not a pointer, reference, nor a pointer-to-data-member type` – Petr May 24 '16 at 16:07
  • 2
    @Petr :/ sad, I'd try `const_cast` without `std::move` also, just to make it simpler. Also - you are sure there is nothing specific inside foo that makes `const` reasonable? – Hcorg May 24 '16 at 16:09
  • @Hcorg, yes, this works. Can you post this as an answer so that I can upvote it? – Petr May 24 '16 at 16:11
  • 2
    don't rely on undefined behavior. switch libraries if needed – David Haim May 24 '16 at 16:16
  • You've declared `bar` with `struct` keyword. Does the type have any private data members? Does it have a non-trivial destructor? If neither, then you could construct a new, non-const instance of `bar`, and copy the const object's state to the new one. – eerorika May 24 '16 at 16:25
  • Please clarify whether `bar` is the real code or not. It makes a difference whether `bar(bar&&) {}` is the real move constructor, or whether it actually does other things in the function. Preferably post real code. – M.M May 25 '16 at 03:15
  • @user2079303, yes, constructing a new non-const instance of `bar` by manually copying contents is an option, though it will result in a rather log code. – Petr May 25 '16 at 08:33
  • @M.M, obviously I can't post the whole code, as it is too long and is not an MVCE, but I do get your point that the implementation of move constructor is important. I've removed the implementation from the question, as I wanted to get a generic answer not assuming any specifics about the move constructor, and that's what the existing answers provide. – Petr May 25 '16 at 08:35
  • 1
    @Petr well, I prefer long code that is guaranteed to work, to short code that might break in the future :) – eerorika May 25 '16 at 08:50

3 Answers3

7

Casting away the const will lead to undefined behavior if the move constructor for bar modifies anything. You can probably work around your issue like this without introducing undefined behavior:

struct wrapped_bar {
    mutable bar wrapped;
};

bar buz()
{
    return foo<wrapped_bar>().wrapped;
}

Having the wrapped member be mutable means that the member is non-const even though the wrapped_bar object as a whole is const. Based on how foo() works, you may need to add members to wrapped_bar to make it work more like a bar.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • +1 Excellent idea! Given that `wrapped_bar` is an implementation detail of `buz()`, it's better defined inside the body of `buz()`. Of course, all this relies on `foo` working for any `T`. – Walter May 31 '16 at 14:33
4

Technically speaking, you are exposing your program to undefined behavior. Since original object C (a temporary) was declared const, const-casting and modifying it is illegal and against the standard. (I assume, move constructor does some modifications to the movee).

That being said, it probably works in your environment and I do not see a better workaround.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • 2
    @DavidHaim, first, it **was** created as `const`. Returned temporary is an individual object, and function signature mandates it to be const. Than, *probably* is based on OP's own statement (*and the whole code work as expected*). – SergeyA May 24 '16 at 16:12
  • 1
    Pure speculation. Saying it "could be undefined behavior" is the only definitive answer here, because OP only cares [about casting const away](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – uh oh somebody needs a pupper May 24 '16 at 16:14
  • @sleeptightpupper, can you please elaborate at which point I am speculating here? – SergeyA May 24 '16 at 16:20
  • I don't const casting causes the UB, it's the fact that if you modify the actual object that was declared const. – David G May 24 '16 at 16:28
  • @0x499602D2, that is indeed true - however, I was answering the specific question with code which has the usage there (most likely, rvalue ctor does some modifications). I will still edit for clarity. – SergeyA May 24 '16 at 16:30
  • I think this is the right answer. I'd create a wrapper for `foo()`, and keep it in a separate translation unit, so that when compiling the caller of the wrapper the compiler can't see that the object was originally const and do something unhelpful. – Martin Bonner supports Monica May 24 '16 at 16:44
  • "and modifying it" - the code posted by OP does not modify the object. But it's possible the real code does modify it and OP's pseudocode is bogus. – M.M May 25 '16 at 03:16
  • @M.M, yeah, I infered modification from the fact that copy ctor is deleted and move constructor is defined instead. – SergeyA May 25 '16 at 13:10
  • *I do not see a better workaround* Consider the [answer](http://stackoverflow.com/a/37427134/1023390) by [Vaughn Cato](http://stackoverflow.com/users/951890/vaughn-cato): it seems to work. – Walter May 31 '16 at 14:26
1

As result of function call is by definition an R-Value itself, you do not need to apply std::move on it in return statement - const_cast<bar&&>(foo<bar>()) should be enough. That make code a little bit simpler to read.

Still - there is no standard guarantee that this will always work for all bar types. Even more - this might in some cases lead to undefined behavior.(Imagine some very intrusive optimization, which completely eradicates foo and makes its result an object in "static data" segment of memory - like if foo was a constexpr. Then calling moving constructor, which probably modifies its argument, might lead to access violation exception).

All you can do is either switch to different library (or, if possible, ask library maintainer to fix API) or create some unit test and include it in your build process - as long as test passes, you should be OK (remember to use same optimization settings as in "production" build - const_cast is one of those things which strongly depends on compilation settings).

Hcorg
  • 11,598
  • 3
  • 31
  • 36