Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to change behavior of function based on scope?

I would like to create something similar to rust unsafe scope in C++. The idea is that I have some functions performing number of checks. For example:

void check() {
     if (...)
        throw exception(...);

}

void foo() {
     check();

     // do some work
}

Now, I want to be able to call function foo() with or (in different context) without performing those checks. Ideally it would look like this:

foo(); // call foo and perform checks
unsafe {
    foo(); // call foo without checks
}

My question is, is it possible to achieve something like this in compile time? Is it possible to somehow check (or act differently) from check function in what scope it is called?

I came up only with a runtime solution: to wrap it in some lambda:

unsafe([&] {
    foo();
});

where unsafe is implemented as follows:

void unsafe(std::function<void()> f)
{
     thread_local_flag = unsafe;
     f();
     thread_local_flag = safe;
}

check() function would just check for the thread_local flag and perform checks only when it is set to safe.

like image 438
Igor Avatar asked Nov 30 '18 08:11

Igor


3 Answers

🤔

namespace detail_unsafe {
    thread_local int current_depth;

    struct unsafe_guard {
        unsafe_guard()  { ++current_depth; }
        ~unsafe_guard() { --current_depth; }

        unsafe_guard(unsafe_guard const &) = delete;
        unsafe_guard &operator = (unsafe_guard const &) = delete;
    };
}

#define unsafe \
    if(::detail_unsafe::unsafe_guard _ug; false) {} else

bool currently_unsafe() {
    return detail_unsafe::current_depth > 0;
}

See it live on Coliru. Also, please don't actually define unsafe as a macro...

like image 123
Quentin Avatar answered Nov 14 '22 23:11

Quentin


is it possible to achieve something like this in compile time?

Not the way you presented. Making foo a template function might give you equivalent results, though:

enum class CallType // find a better name yourself...
{
    SAFE,
    UNSAFE,
};

template <CallType Type = CallType::SAFE>
void foo()
{
    if constexpr(Type != CallType::UNSAFE)
    {
        if (...)
            throw ...;
    }
    // do some work
}

You might call it like:

foo();
foo<CallType::UNSAFE>();

Disliking templates?

Simple approach (thanks, @VTT):

void check(); // no template any more

void foo_unsafe()
{
    // do some work
}
inline void foo()
{
    check();
    foo_unsafe();
}

Or selecting via parameter (this pattern exists in standard library, too):

struct Unsafe
{
};
inline Unsafe unsafe;

void check();

void foo(Unsafe)
{
    // do some work
}
inline void foo()
{
    check();
    foo(unsafe);
}

Edit:

Well, in the example I presented I could do that, but in general, I can call some other function bar inside unsafe which in turn calls foo. And I don't want to specialize bar and possible other methods.

Unter this constraint, the template variant might be the closest you can get to at compile time; you don't have to specialise all the functions, but you'd need to make templates from:

template <CallType Type = CallType::SAFE>
void bar()
{
    // do some other work
    foo<Type>(); // just call with template parameter
    // yet some further work
}
like image 38
Aconcagua Avatar answered Nov 14 '22 22:11

Aconcagua


I would simply use a RAII type to toggle the unsafe flag inside a scope as such:

thread_local bool unsafe_flag = false;

/// RAII Type that toggles the flag on while it's alive
/// Possibly add a reference counter so it can be used nested
struct unsafe_scope
{
    constexpr unsafe_scope() { unsafe_flag = true; }
    ~unsafe_scope()          { unsafe_flag = false; }
};

/// Gets a value from a pointer
int get_value(int* ptr)
{
    if ( unsafe_flag )
    {
        if ( ptr == nullptr ) { return 0; }
    }

    return *ptr;
}

int main()
{
    int* x = nullptr;

    //return get_value(x); // Doesn't perform the check

    {
        unsafe_scope cur_scope;
        return get_value(x); // Performs the check
    }
}

In order to make it nested I would add a reference counter like this:

/// RAII Type that toggles the flag on while it's alive
struct unsafe_scope
{
    thread_local static size_t ref_count;

    constexpr unsafe_scope()
    {
        unsafe_flag = true;
        ref_count++;
    }
    ~unsafe_scope()
    {
        ref_count--;
        if ( ref_count == 0 ) { unsafe_flag = false; }
    }
};

/// In source file
thread_local size_t unsafe_scope::ref_count = 0;

The ref_count doesn't need to be atomic since it's thread_local

Now I don't think there's a way to achieve the syntax you wanted with the unsafe before the scope, but if you put it right after the scope as such it should be about the same:

{ unsafe_scope cur_scope;
    return get_value(x); // Performs the check
}

Edit:

I've now noticed Quentin's answer is also a RAII type, just with slightly different semantics, instead of having a global thread_local flag a function just returns if the reference counter is bigger than 0. Also the macro achieves the exact syntax you wanted, although it's also possible with this unsafe_scope by modifying his macro like this:

#define unsafe\
    if (unsafe_scope cur_scope; false) {} else 

His method uses C++17's if initializer, which lets you initiates a variable in the if statement, but the variable is still initialized in the else block, so it only gets destroyed after the else scope if over.

like image 29
Filipe Rodrigues Avatar answered Nov 14 '22 22:11

Filipe Rodrigues