7

I have my own class where I define and use << operator like so:

vw& vw::operator<<(const int &a) {

    // add a to an internal buffer
    // do some work when it's the last number

    return *this;
}

...

vw inst;

inst << a << b << c << d;

...

inst << a << b;

...

The number of chain << calls is different every time. These numbers together represent a code and I need to do something when the code is complete.

Do I have any other options to know when it's complete rather than adding a special terminating value to each chain, like below?

inst << a << b << c << d << term;

...

inst << a << b << term;

EDIT2 : current solution following LogicStuff answers:

--- chain.h ---
#pragma once
#include <iostream>
class chain
{
public:
    chain();
    ~chain();
};
--- chain_cutter.h ---
#pragma once
#include "chain.h"
class chain_cutter
{
    chain &inst;
public:
    explicit chain_cutter(chain &inst) : inst(inst) {
        std::cout << "cutter_constructor" << std::endl;
    }
    ~chain_cutter();
};
--- chain_cutter.cpp ---
#include "stdafx.h"
#include "chain_cutter.h"
chain_cutter::~chain_cutter()
{
    std::cout << "cutter_destructor" << std::endl;
}
--- chain.cpp ---
#include "stdafx.h"
#include "chain.h"
chain::chain()
{
    std::cout << std::endl << "chain_constructor" << std::endl;
}
chain::~chain()
{
    std::cout << std::endl << "chain_destructor" << std::endl;
}
--- flowchart.cpp ---

#include "stdafx.h"

#include <iostream>

#include "chain.h"
#include "chain_cutter.h"

chain_cutter operator<<(chain &inst, const int &a) {
    chain_cutter cutter(inst);
    std::cout << a << std::endl;
    return cutter;
}

chain_cutter&& operator<<(chain_cutter &&cutter, const int &a) {
    std::cout << a << std::endl;
    return std::move(cutter);
}

int main()
{

    std::cout << "main start" << std::endl;

    chain ch;

    ch << 1 << 2 << 3;

    std::cout << std::endl << "-----" << std::endl;

    ch << 4 << 5;

    return 0;
}

This is the output:

main start

chain_constructor
cutter_constructor
1
2
3
cutter_destructor

-----
cutter_constructor
4
5
cutter_destructor
user1481126
  • 371
  • 1
  • 10
  • 1
    You'll need a terminator if you go the streaming route. You could make a variadic function and handle doing that in the function. – NathanOliver Oct 08 '18 at 16:20

2 Answers2

6

It is possible without changing the current syntax.

You will have to make inst << a (i.e. the current operator<<) return a temporary instance of a "special" class, holding a reference to inst, implementing operator<< by calling inst.operator<<, returning reference to *this, and then doing the extra work in its destructor, which will be called at the end of the statement.

And yes, you can keep track of the call count with it.


I propose these nonmember operator<< overloads (vw_chain is the new proxy class):

// Left-most operator<< call matches this
vw_chain operator<<(vw &inst, const int &a) {
    return vw_chain(inst, a);
}

// All subsequent calls within the << chain match this
vw_chain &&operator<<(vw_chain &&chain, const int &a) {
    chain.insert(a);
    return std::move(chain);
}

The class itself:

struct vw_chain
{
    explicit vw_chain(vw &inst, const int &a) :
        inst(inst)
    {
        insert(a);
    }

    ~vw_chain() {
        // do something
    }

    void insert(const int &a) {
        // This, the original operator<<, should be made accessible only to this
        // function (private, friend class declaration?), not to cause ambiguity.
        // Or, perhaps, put the implementation of the original operator<< here
        // and remove it altogether.
        inst << a;
        ++insertion_count;
    }

    vw &inst;
    size_t insertion_count = 0;
};

We have to pass the instance around by rvalue reference. We do the first insertion inside the vw_chain's constructor, in order to get the mandatory copy elision (C++17), which works only with prvalues. Whether there will be a copy done at the return statement, is unspecified with NRVO and older standards. We should not rely on that.

Pre-C++17 solution:

struct vw_chain
{
    // We keep the constructor simpler
    vw_chain(vw &inst) : inst(inst) {}

    // Moved-from chains are disabled
    vw_chain(vw_chain &&other) :
        inst(other.inst),
        insertion_count(other.insertion_count) {
        other.is_enabled = false;
    }

    // And will not call the termination logic
    ~vw_chain() {
        if(is_enabled) {
            // do something
        }
    }

    void insert(const int &a) {
        inst << a;
        ++insertion_count;
    }

    vw &inst;
    size_t insertion_count = 0;
    bool is_enabled = true;
};

// The first overload changes to this
vw_chain operator<<(vw &inst, const int &a) {
    vw_chain chain(inst);
    chain.insert(a);
    return chain;
}
LogicStuff
  • 18,687
  • 6
  • 49
  • 70
5

You can:

  • Use a special final type to signal termination (a la std::endl).

  • Avoid using operator<<, and defining a variadic template function instead. A variadic template function supports any number of arbitrary arguments, and knows that number at compile-time.

  • Put the logic into vw's destructor, and allow operator<< only on rvalues. E.g.

    vw{} << a << b << c;
    // `vw::~vw()` contains termination logic
    
Vittorio Romeo
  • 82,972
  • 25
  • 221
  • 369