1

tl;dr: Is there a way to add a default argument from the current scope to all implicit constructors in C++?

I am currently designing an interface for an embedded language in C++. The goal is to make the creation of syntactically correct expressions both typesafe and convenient. Right now, I think that learning a heavyweight implementation like boost::proto will itnroduce a too large latency into the development, so I attempt to roll my own implementation.

Here is a small demo:

#include <iostream>
#include <string>
#include <sstream>

class ExprBuilder
{
public:
    ExprBuilder(const int val) : val(std::to_string(val)) {}
    ExprBuilder(const std::string val) : val(val) {}
    ExprBuilder(const char* val) : val(val) {}

    ExprBuilder(const ExprBuilder& lhs, const ExprBuilder& arg) {
        std::stringstream ss;
        ss << "(" << lhs.val << " " << arg.val << ")";
        val = ss.str();
    }

    const ExprBuilder operator()(const ExprBuilder& l) const {
        return ExprBuilder(*this, l);
    }

    template<typename... Args>
    const ExprBuilder operator()(const ExprBuilder& arg, Args... args) const
        {
            return (*this)(arg)(args...) ;
        }

    std::string val;
};

std::ostream& operator<<(std::ostream& os, const ExprBuilder& e)
{
    os << e.val;
    return os;
}

int main() {
    ExprBuilder f("f");
    std::cout << f(23, "foo", "baz") << std::endl;
}

As you can see, it is fairly simple to embedd expressions due to C++ overloading and implicit conversions.

I am facing a practical problem however: In the example above, all data was allocated in the form of std::string objects. In practice, I need something more complex (AST nodes) that are allocated on the heap and managed by a dedicated owner (legacy code, cannot be changed). So I have to pass a unique argument (said owner) and use it for the allocations. I'd rather not use a static field here.

What I am searching is a way to use ask the user to provide such an owner everytime the builder is used, but in a convenient way. Something like a dynamically scoped variable would be great. Is there a way to obtain the following in C++:

class ExprBuilder
{
    ...
    ExprBuilder(const ExprBuilder& lhs, const ExprBuilder& arg) {
        return ExprBuilder(owner.allocate(lhs, rhs)); // use the owner implicitly
    }
    ...
};

int main() {
    Owner owner; // used in all ExprBuilder instances in the current scope
    ExprBuilder f("f");
    std::cout << f(23, "foo", "baz") << std::endl;
}

Is this possible?

edit: I'd like to clarify why I do (up until now) not consider a global variable. The owner has to be manually released by the user of the builder at some point, hence I cannot create one ad hoc. Hence, the user might "forget" the owner altogether. To avoid this, I am searching a way to enforce the presence of the owner by the typechecker.

choeger
  • 3,562
  • 20
  • 33
  • I think what you are saying is some kind of `global variable` – apple apple Mar 16 '17 at 12:13
  • You can set a static/global variable with `Owner` Raii, and use that global variable in `ExprBuilder`. – Jarod42 Mar 16 '17 at 12:15
  • You mean using a unique_ptr as the global variable in order to ensure that it is only used in the context and has to be retained explicitly? Wouldn't that yield runtime errors, if the pointer is forgotten by the user? – choeger Mar 16 '17 at 12:25
  • @appleapple The OP has already said "I'd rather not use a static field here". Static member variables are one particular flavour of global variables. I strongly suspect his objections will apply to any other flavour. – Martin Bonner supports Monica Mar 16 '17 at 12:25
  • @choeger: I think the idea is that you have a global variable (possibly a pointer to something if this is legacy code), and then a RAII class to set that global variable *and unset it afterwards*. – Martin Bonner supports Monica Mar 16 '17 at 12:27
  • @choeger You can pass owner to the `ExprBuilder`'s constructor (not every time it's used as you say), I see no inconvenience. – apple apple Mar 16 '17 at 12:28
  • Could you elaborate on how the implicit conversions would work then? – choeger Mar 16 '17 at 12:30
  • @choeger for your edit, it's just what RAII handles for you(in some way). – apple apple Mar 16 '17 at 12:31
  • @choeger for your comment, I don't see where implicit conversions is needed. – apple apple Mar 16 '17 at 12:33
  • Of course, they are not needed per se ;), but just look at the example, I **want** it to be convenient to insert ints, strings etc. in the builder in a concise form. – choeger Mar 16 '17 at 12:47

2 Answers2

1

This is hardly possible without global / static variables, because without global / static information, the local variables Owner owner and ExprBuilder f cannot know anything about each other.

I think the cleanest way is to add a

static Owner* current_owner;

to the ExprBuilder class. Then you can add a new class ScopedCurrentOwnerLock, which sets the current_owner in the constructor and sets it to nullptr in the destructor. Then you can use it similar to a mutex lock:

class ScopedCurrentOwnerLock {
public:
    ScopedCurrentOwnerLock(Owner const& owner) {
        ExprBuilder::current_owner = &owner;
    }
    ~ScopedCurrentOwnerLock() {
        ExprBuilder::current_owner = nullptr;
    }
};

int main() {
    Owner owner;
    ScopedCurrentOwnerLock lock(owner);
    ExprBuilder f("f");
}

If you have access to the Owner code, you can omit the ScopedCurrentOwnerLock class and directly set / unset the pointer in the constructor/destructor of Owner.

Please be aware of the following two problems with this solution:

  • If the owner goes out of scope before the lock goes out of scope, you have an invalid pointer.

  • The static pointer has unpredictable behaviour if you have multiple locks at the same time, e. g. due to multithreading.

pschill
  • 5,055
  • 1
  • 21
  • 42
0

All your ExprBuilders have a dependancy on Owner, and you rightly don't want global state. So you have to pass owner to every constructor.

If you really don't want to add owner, to all your instantiations in a block, you can create a factory to pass it for you.

struct ExprBuilderFactory
{
    Owner & owner;
    ExprBuilder operator()(int val) { return ExprBuilder(owner, val); }
    ExprBuilder operator()(char * val) { return ExprBuilder(owner, val); }
    // etc
}

int main() {
    Owner owner;
    ExprBuilderFactory factory{ owner };
    ExprBuilder f = factory("f");
}
Caleth
  • 52,200
  • 2
  • 44
  • 75