2

In C++, if I write

token make_token() { return token{}; }

and then use it as follows

void use_token()
{
  make_token();
  // extra code
}

without assigning a token to a variable, token's destructor fires before extra code executes. How can I get the destructor to only fire at the end of the function without having to make a variable?

Note: I want to completely avoid making a variable. I know I can do auto& t = make_token() or similar, but I want to avoid precisely this by returning something (I don't know what) that doesn't have the destructor fired immediately.

Why I want this: basically, in my app (a compiler for a programming language) I have these things called tokens. A token's constructor can put a { and indent, and its destructor can then put } and un-indent. I thought it a good idea to set up functions which return these tokens by value, but I don't actually want to assign them to any value, since the tokens are useless and have no functions.

To alleviate confusion, my token is not a lexical token. I use the work token in lieu of the work cookie. It's meant to do something in the constructor, wait until the end of its scope, and then do something in its destructor. That's it. By the way, if I was writing this in C#, I would write something like

 using (make_token())
 {
   // my code here
 }

and it would work as intended. But it turns out that something so simple is difficult in C++.

Dmitri Nesteruk
  • 23,067
  • 22
  • 97
  • 166
  • 1
    I don't think you can. – melpomene Sep 08 '16 at 20:36
  • 2
    Attaching the return value to an identifier is the way to tell the compiler not to destroy the value immediately. – Galik Sep 08 '16 at 20:42
  • 4
    I think a better question is, why don't you want to assign it to a variable? If you are worried about it being wasteful, don't worry. Compilers are smart, they never generate the code you write anyway. Simply assigning it to a variable or const ref and never using it again is good enough to tell the compiler "I'm not going to use this, but the destructor shouldn't run until this falls out of scope." – RyanP Sep 08 '16 at 20:44
  • I have added more info on _why_ i want this. – Dmitri Nesteruk Sep 08 '16 at 20:50
  • 3
    Your reasoning does not make any sense. – Slava Sep 08 '16 at 20:52
  • @DmitriNesteruk If you don't care about the return, because it is useless and has no value... then just assign it to a variable or a const reference and don't use that. It will go out of scope and call the destructor as intended at the correct time. If you need it to go out of scope earlier than the end of your current function you can also add blocks inside the function to manipulate the lifetime of those objects as well. – RyanP Sep 08 '16 at 20:53
  • 3
    If it's difficult in C++, that's only because you're refusing to use the standard way of doing so. RAII is C++'s automatic resource cleanup mechanism, and `using` is C#'s. You've refused to use RAII here, so that leaves...awkward hacks. Because no one has replaced RAII when it works so well. – jaggedSpire Sep 08 '16 at 21:01
  • @DmitriNesteruk Check out Daniel Schepler's answer using `keep_alive`. That's basically the same code shape as `using`. – melpomene Sep 08 '16 at 21:02
  • @melpomene it's not quite the same code because it involves the creation of an additional function that I do not need; note that in the C# example, no extra function is created – Dmitri Nesteruk Sep 08 '16 at 21:07
  • As I thought we have C# developers that are trying to write C# code in C++ and think that would make it easier to use. Facepalm. – Slava Sep 08 '16 at 21:08
  • @Slava I think you'll find SO the wrong platform for making condescending statements. – Dmitri Nesteruk Sep 08 '16 at 21:09
  • 1
    @DmitriNesteruk You can reduce the number of functions back to 1 by fusing `keep_alive` and `make_token` into `using_token([&]{ /* ... */ })`. – melpomene Sep 08 '16 at 21:10
  • @DmitriNesteruk tell that your colleague about "miserable little pile of secrets!" and condescending statements – Slava Sep 08 '16 at 21:12
  • You should really just use a variable. see the usage of e.g. std::unique_lock and related "token"-type things – Sean Sep 08 '16 at 21:38

6 Answers6

8

Yes. You can use a constant reference. This is called most important const in C++, and it's a feature that's not widely known.

Here's how you do it:

void use_token()
{
  const token& myToken = make_token();
  // now myToken is alive until the end of this function.
}

But you have to return strictly by value for this to work (you do that in the code you provided).

People who don't believe this, please try it yourself before attacking the post.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 3
    Umm, I want to explicitly avoid assigning a token to a variable. – Dmitri Nesteruk Sep 08 '16 at 20:41
  • @DmitriNesteruk This is not a variable. This is a reference to the same variable you created inside the other function. So no copying is happening. – The Quantum Physicist Sep 08 '16 at 20:42
  • I understand, the point it I want to avoid making any sort of declarations. I just want to write `make_token()` and have the destructor at the very end. – Dmitri Nesteruk Sep 08 '16 at 20:43
  • @DmitriNesteruk To be honest I don't see how you can use that at all! In that case you have to use pointers and delete them yourself. But trust me, you're better off using this constant reference solution. It's explicitly made for the sort of issue you're talking about. – The Quantum Physicist Sep 08 '16 at 20:45
  • @DmitriNesteruk, what harm does writing an extra variable do to you? ...There's no work done there, except to extend the lifetime of the object as you wanted? – WhiZTiM Sep 08 '16 at 20:45
  • With C++11 and later, `auto&& myToken = make_token();` does the same thing (though as with this answer, you do have to be careful that what you're assigning is directly an rvalue rather than, for example, the return value of a function returning an rvalue reference, or a static_cast to an rvalue reference). – Daniel Schepler Sep 08 '16 at 20:45
  • @TheQuantumPhysicist That's still a variable. – melpomene Sep 08 '16 at 20:46
  • @melpomene Honestly I didn't know that even a reference is not desirable. I'm shocked! – The Quantum Physicist Sep 08 '16 at 20:46
  • What do you want to do with the return value if you don't want to assign it to a variable? Are you trying to write `some_function(make_token())`? – Barmar Sep 08 '16 at 20:46
  • @Barmar Nothing. The object should just stay alive until the function ends. – melpomene Sep 08 '16 at 20:47
  • @melpomene Honestly I don't see why assigning a reference to it (or moving it in C++11) is a problem. This is the weirdest request I have ever faced! – The Quantum Physicist Sep 08 '16 at 20:48
  • 1
    @TheQuantumPhysicist You clearly haven't been to Vegas – RyanP Sep 08 '16 at 20:49
  • @RyanP True xD... hehe! – The Quantum Physicist Sep 08 '16 at 20:49
  • @TheQuantumPhysicist It requires the programmer to come up with a unique name, assign (or bind) `make_token()` to it, and then never touch that variable again. It would make for an easier API if you could just say "call `make_token()` at the start of your function" and the rest is automatic, without cooperation from the user of the API. – melpomene Sep 08 '16 at 20:52
  • 2
    @melpomene no it will not make an easier API, it would make it difficult to understand and unobvious – Slava Sep 08 '16 at 20:56
  • 1
    I agree with @Slava, you could do some macro magic to create the API you want which will create variables in the background, but then it adds side-effects to the code which are not-obvious and non-intuitive. The extra time it will take someone to figure that out will negate any savings your API could have introduced by using a little less typing. – RyanP Sep 08 '16 at 20:57
  • @Slava Hah. Perhaps the same could be said of all C++ features. – melpomene Sep 08 '16 at 20:58
  • I agree with Salava, too. Solutions provided with C++11 are very complicated with no obvious reason. Most important const is used exactly for the purpose of extending the life of a return value. – The Quantum Physicist Sep 08 '16 at 20:59
  • 2
    @melpomene if you do not understand C++ then do not write API in this language. But if you are writing API for this language indeed to create "smart" dumb solutions like this which are unobvious for a C++ developer will complicate things for no another reason, than for you to show your "smartness". – Slava Sep 08 '16 at 21:02
  • @Slava What is a C++ developer? A miserable little pile of secrets! But enough talk! Have at you! – melpomene Sep 08 '16 at 21:04
  • @melpomene The same could be said about C or any other language. That's why there are idioms and best practices in any programming language, so that most people can get a quick sense of what is going on by looking at common patterns. – RyanP Sep 08 '16 at 21:04
  • @melpomene if you only know C# why are you writing C++ API? – Slava Sep 08 '16 at 21:09
8

Like this:

make_token(),
[](){ /* extra stuff */ }();

Make sure you wash your hands afterwards :)

jrok
  • 54,456
  • 9
  • 109
  • 141
  • 5
    And the name of that is the "Lambda of doom" – deW1 Sep 08 '16 at 20:46
  • Whoa, this is scary stuff. Can you explain _why_ it works? – Dmitri Nesteruk Sep 08 '16 at 20:47
  • A mundane two character shorter version will be: `make_token(), []{ /* extra stuff */ }();` ...:-) – WhiZTiM Sep 08 '16 at 20:48
  • @DmitriNesteruk It moves the rest of the code into a temporary function that is invoked immediately, as part of the same expression `make_token` is called in. The returned object is only destroyed at the end of the full expression. Using this trick we've arranged for the end of the expression to also be the end of the surrounding function. – melpomene Sep 08 '16 at 20:49
  • 1
    While this is an interesting approach, I have too many places, including nested places, where I need to do this. So this solution is not for me, but upvoted for sheer cleverness. – Dmitri Nesteruk Sep 08 '16 at 20:51
  • @DmitriNesteruk It's indeed a bit of a tongue-in-cheek answer. Your real problem is the design of your class. A class that represents a lexical token has no bussiness indenting code around itself, IMO. – jrok Sep 08 '16 at 20:54
  • Am I missing something, or is that equivalent to `operator,(make_token(), []{ ... });`? Because if it is, then order of evaluation of function attributes is not determined (but sequential). You _are_ delaying destructor, but you might as well delay constructor... – lorro Sep 08 '16 at 20:54
  • @lorro The order of evaluation of operands of `operator,` is left-to-right. – jrok Sep 08 '16 at 20:56
  • Also, the lambda cannot use `return` statement of the calling context (we're working on that).. – lorro Sep 08 '16 at 21:11
  • @lorro It can't, but why is that a problem here? – jrok Sep 08 '16 at 21:16
  • @jrok: as for the comma, yep, you're right. As for what's the problem, well.. OP wants to use it in a function. A function might have many points-of-return, including ones within the part where the token is desired to exist. If you say that the token exists all the way to the end of the function (and how do you ensure that?), you still have the problem of not supporting multiple tokens.. – lorro Sep 08 '16 at 21:31
4

If you're able to use C++11 or later, you could write a template function something like:

template <typename T, typename Functor>
void keep_alive(T&&, Functor f) {
    f();
}

...
void use_token() {
    keep_alive(make_token(), [&] {
        // rest of body of function
    });
}

Edit after the clarification of why it's wanted:

For the specific use case of creating a token to put in { } and indent, you could create a wrapper function specifically named to make it clear what's happening:

template <typename Functor>
void make_indented_block(Functor f) {
    auto indentToken = make_token();
    f();
}
Daniel Schepler
  • 3,043
  • 14
  • 20
2

We have classical XY problem here:

So for C# code:

using (make_token())
{
  // my code here
}

create a class token:

class token {
public:
    token() { /* calling make_token(); */ }
    ~token() { /* destroying token */ }
};

then use it:

{
    token tok;
    // some stuff here
    {
        token tok;
        // some other stuff here
    }
}

So

  1. This usage will be clear for C++ developers and your C++ API will be easy to use for them.
  2. Your argument about problem creating unique variable name is wrong, as shown you can use the same name.
  3. You do not have to tell anybody not to use this variable, as it only has constructor and destructor.

If necessary you can put that into macro, but personaly I would find it more difficult to use.

MAChitgarha
  • 3,728
  • 2
  • 33
  • 40
Slava
  • 43,454
  • 1
  • 47
  • 90
1

Well, you may think you can receive the value returned by that function;

void use_token()
{
  auto nonsense = make_token();
  // extra code
}

Even with this, did you know that (Pre-C++17) ... There was still possibility for two destructor calls there when RVO doesn't take place?

Taking it by const reference as The Quantum Physicist's answer says is the best way out.

Community
  • 1
  • 1
WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
0

I feel your pain, auto = make_token() would be useful. However...

You might have the XY-problem. Shouldn't you do instead:

with_token([&]{ ... });

I.e., the token generator/constructor taking a lambda? This should work if you don't want to write return within the lambda to return from the actual function creating the token.

Another approach is, if you just want no-one to 'know the name', the notorious for-pattern:

template<typename T>
struct Keeper
{
    const T t;
    char b;
    Keeper(const T& t_)
        : t(t_) {}
    char* begin() { return &b; }
    char* end()   { return begin() + 1; }
};

template<typename T>
auto make_keeper(const T& t)
{
    return Keeper<T>(t);
}

void f()
{
    for (char c : make_keeper(make_token()))
    {
        // now try to name t or Keeper<T>(t) here
    }
}

You might add move schematics and perfect fwd if you wish.

lorro
  • 10,687
  • 23
  • 36