How to check in C++ that identifier is declared?

Viewed 367

I want to check at some point of code if some identifier x is declared, how do I do that?

I need this kind of check for different types of identifiers - variables, enum constants, functions, types, macros, etc. But for start I want to check at least variables and functions.

I need such kind of check (e.g. imaginary declared(x)) so that next code works e.g. for the case of int variable x:

if constexpr(declared(x)) {
    int y = x + 1;
} else {
    std::cout << "Variable 'x' not declared!" << std::endl;
}

For the case of macros of cause I can use #ifdef x, but how to do same check for variables/functions?

For the case of global non-lambda functions I figured out next code, based on overloaded functions resolution, but it needs using helper macros-based global definitions (can it be simplified more?):

Try it online!

#include <iostream>
#include <type_traits>

#define declared_func_helper(x, ...) \
    struct NotDeclared; \
    template <typename ... Args> \
    NotDeclared x(Args ...); \
    template <typename ... Args> \
    inline constexpr bool declared_func_##x(Args && ... args) { \
        return !std::is_same_v<decltype(x(args...)), NotDeclared>; \
    }

// declare some of functions
//void f(int a) {}
void g(bool b, char c) {}
    
// define helpers before or after declared functions
declared_func_helper(f);
declared_func_helper(g);

int main() {
    // check declaration of functions
    std::cout << "func 'f' declared: " << std::boolalpha << declared_func_f(int()) << std::endl;
    std::cout << "func 'g' declared: " << std::boolalpha << declared_func_g(bool(), char()) << std::endl;
}

which outputs:

func 'f' declared: false
func 'g' declared: true

For the case of non-global variables I implemented next code, but it also needs helpers definition through macroses:

Try it online!

#include <type_traits>
#include <iostream>

#define declared_var_helper(x) \
    struct NotDeclared_##x {}; \
    NotDeclared_##x x;
#define declared_var(x) \
    ([&](){ return !std::is_same_v<decltype(x), NotDeclared_##x>; }())
    
// use helpers before variables declaration
declared_var_helper(x);
declared_var_helper(y);

int main() {
    // declare some of variables
    //bool x = false;
    int y = 0;
    // ........
    // later check declaration
    constexpr bool is_declared_x = declared_var(x), is_declared_y = declared_var(y);
    std::cout << std::boolalpha << "var 'x' declared: " << is_declared_x << std::endl;
    std::cout << "var 'y' declared: " << is_declared_y << std::endl;
}

which outputs:

var 'x' declared: false
var 'y' declared: true

What about other cases, or easier ways to check?

2 Answers

What you can do for your function issue is to declare a global function pointer which your client has to fill in, and is set to your function by default.

For example:

const auto Function = Function_Fallback

and then use Function everywhere.

This expands on the comment by @500-internal-server-error.

From your comment, I understand that you are trying to add customization points to your library.

The canonical way to do this in OOP is to define interface (base classes) for what you want to do, and add default implementations for them.

Then, specify (through documentation and examples) how users of your code can customize this behavior to fit their needs.

For this, you do not need to check if/what clients declare in their code.

Here is an example (using some hypotetical classes to serialize data):

class Document // your data class, that you needd to save
{
    int a = 0; // document data (that needs to be saved)
    int b = 1;
public:
     void save() // by default, save to xml
     {
         XmlDocument store{"document.xml"};
         save(store);
     }

     void save(OutputDocument &store)
     {
         store.add("a", to_string(a));
         store.add("b", to_string(b));
     }
};

customization point (OutputDocument):

class OutputDocument
{
public:
     virtual void add(const std::string& name, const std::string& value) = 0;
};

default implementation (xml):

class XmlDocument: public OutputDocument
{
public:
     void add(const std::string& name, const std::string& value) override; 
};

Customization (clients can do this):

class JsonDocument: public OutputDocument // store to JSON instead
{
public:
     void add(const std::string& name, const std::string& value) override; 
};

Clients can just say:

Document doc;
JsonDocument json{"document.json"};
doc.save(json); // save to json instead of the default (to xml)
Related