3
template<typename T, typename U>
void add_property_immutable(T property_name, U property_value) {
    auto get_v8_data_type = []<typename X>(X&& property_name_or_value) -> int {
        using removed_reference = std::remove_reference<X>::type;
        using removed_pointer = std::remove_pointer<removed_reference>::type;
        using removed_const = std::remove_const<removed_pointer>::type;
        if(std::is_same_v<bool, removed_const>) { return BOOL; }
        else if(std::is_same_v<int, removed_const>) { return INTEGER;}
        else if(std::is_same_v<char, removed_const>) { return CHAR; }
        else if(std::is_same_v<double, removed_const> || std::is_same_v<float, removed_const>) { return NUMBER; }
        else if(std::is_same_v<std::string, removed_const>) { return STRING; }
        return -1;
    };
    int property_name_data_type = get_v8_data_type(property_name);
    if (property_name_data_type == CHAR) {
        property_name_string = std::string(property_name);
    }
    else if(property_name_data_type == STRING) {
        property_name_string = property_name;
    }
    else {
        throw std::invalid_argument("slim::plugin::plugin::add_property_immutable requires a string or char type for the property name");
    }
    int property_value_data_type = get_v8_data_type(property_value, true);
    if (property_value_data_type == BOOL) {
        plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), v8::Boolean::New(isolate, property_value), v8::ReadOnly);
    }
    else if(property_value_data_type == CHAR || property_value_data_type == STRING) {
        plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), slim::utilities::StringToV8String(isolate, property_value), v8::ReadOnly);
    }
    else if(property_value_data_type == INTEGER) {
        plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), v8::Integer::New(isolate, property_value), v8::ReadOnly);
    }
    else if(property_value_data_type == NUMBER) {
        plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), v8::Number::New(isolate, property_value), v8::ReadOnly);
    }
    else {
        throw std::invalid_argument("slim::plugin::plugin::add_property_immutable requires a primitive type for the property value");
    }
}

These calls from an external source:

os_plugin.add_property_immutable("test_var1", "test string");
os_plugin.add_property_immutable("number", 5);

Generate compiler errors like these:

os_plugin.add_property_immutable("test_var1", "test string")
error: invalid conversion from 'const char*' to 'int32_t' {aka 'int'} [-fpermissive]
plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), v8::Integer::New(isolate, property_value), v8::ReadOnly);

error: cannot convert 'const char*' to 'double'
plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), v8::Number::New(isolate, property_value), v8::ReadOnly)

os_plugin.add_property_immutable("number", 5);
error: could not convert 'property_value' from 'int' to 'std::string' {aka 'std::__cxx11::basic_string<char>'}
plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), slim::utilities::StringToV8String(isolate, property_value), v8::ReadOnly);

This error occurs when compiling using:

if constexpr (property_value_data_type == BOOL)
error: the value of ‘property_value_data_type’ is not usable in a constant expression
if constexpr (property_value_data_type == BOOL)

note: ‘int property_value_data_type’ is not const
int property_value_data_type = get_v8_data_type(property_value, true);

I have tried using a switch statement, which also gives the same errors. I am wondering what I have done wrong with this? It seems like the compiler is hitting every if/else in the chain instead of stopping on the correct if/else block.

I have tried making the if block a constexpr, but another set of errors crops up.

The uppercased variables are macros assigned int values. I have made them const int expressions for them, with no change in results.

1
  • "I have tried making the if block a constexpr, but another set of errors crops up.". Please include the snippet and errors for that case as well. Commented Apr 27 at 10:17

3 Answers 3

6

You need to use constexpr if to discard instantiations in untaken branches. So change if to if constexpr when the condition is a constant expression involving the dependent type.

Overall, I would suggest using function overloads instead of a single function template though.

void add_property_immutable(std::string name, bool value) {
    plugin_template->Set(
        slim::utilities::StringToV8String(isolate, name),
        v8::Boolean::New(isolate, value),
        v8::ReadOnly);
}

void add_property_immutable(std::string name, std::string value) {
    plugin_template->Set(
        slim::utilities::StringToV8String(isolate, name),
        slim::utilities::StringToV8String(isolate, value),
        v8::ReadOnly);
}

void add_property_immutable(std::string name, int value) {
    plugin_template->Set(
        slim::utilities::StringToV8String(isolate, name),
        v8::Integer::New(isolate, value),
        v8::ReadOnly);
}

void add_property_immutable(std::string name, float value) {
    plugin_template->Set(
        slim::utilities::StringToV8String(isolate, name),
        v8::Number::New(isolate, value),
        v8::ReadOnly);
}
Sign up to request clarification or add additional context in comments.

5 Comments

That gives an error also => error: the value of ‘property_value_data_type’ is not usable in a constant expression 112 | if constexpr (property_value_data_type == BOOL) {
I've been trying to learn more about using templates. When the errors started I resisted the urge to create function overloads so that I can understand how to fix the errors. "if constexpr(...) did not fix the example code.
@JeffGreer that's because get_v8_data_type is data-dependent and so you can't declare constexpr int property_value_data_type = get_v8_data_type(property_value, true);. You'd need to either write the function to deal with T and U directly, or accept it through a std::type_identity<...> parameter so it can be called as a constant expression.
I can appreciate wanting to learn more about templates. Part of learning them should also be recognizing when not to use them though. Understand that this is an XY problem and you're trying to get help on a particular solution you've already chosen rather than the actual problem you're trying to solve.
Fair enough. Thank you for pointing that out. overloads it is.
5

When performing comparisons at compile-time, use if constexpr instead of if. if is evaluated at runtime, and so its branches must be valid at compile-time. if constexpr, on the other hand, allows you to do comparisons at compile-time and eliminate unused branches.

So, don't bother converting types into integers that are compared at runtime, just compare the types directly at compile-time instead.

Try something more like this:

template<typename T, typename U>
void add_property_immutable(T property_name, U property_value) {

    using property_name_data_type = std::remove_cvref_t<std::remove_pointer_t<T>>;
    if constexpr (std::is_same_v<char, property_name_data_type>) {
        property_name_string = std::string(property_name);
    }
    else if constexpr (std::is_same_v<std::string, property_name_data_type>) {
        property_name_string = property_name;
    }
    else {
        throw std::invalid_argument("slim::plugin::plugin::add_property_immutable requires a string or char type for the property name");
    }

    using property_value_data_type = std::remove_cvref_t<std::remove_pointer_t<U>>;
    if constexpr (std::is_same_v<bool, property_value_data_type>) {
        plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), v8::Boolean::New(isolate, property_value), v8::ReadOnly);
    }
    else if constexpr (std::is_same_v<char, property_value_data_type> || std::is_same_v<std::string, property_value_data_type>) {
        plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), slim::utilities::StringToV8String(isolate, property_value), v8::ReadOnly);
    }
    else if constexpr (std::is_same_v<int, property_value_data_type>) {
        plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), v8::Integer::New(isolate, property_value), v8::ReadOnly);
    }
    else if constexpr (std::is_same_v<double, property_value_data_type> || std::is_same_v<float, property_value_data_type>) {
        plugin_template->Set(slim::utilities::StringToV8String(isolate, property_name_string), v8::Number::New(isolate, property_value), v8::ReadOnly);
    }
    else {
        throw std::invalid_argument("slim::plugin::plugin::add_property_immutable requires a primitive type for the property value");
    }
}

7 Comments

"don't bother converting types into integers that are compared at runtime, just compare the types directly at compile-time instead." definitely the most useful thing to point out. I wish I'd articulated that.
This pattern is easy to read, especially in a year.
There's no need for a runtime exception. A static_assert(false,...) for when none of of the if constexprs matches would make it into a compile time error instead.
@PatrickRoberts static_assert(false, is ok in all versions. It was a defect that has since been corrected. Older compilers may still need an always_false_v helper but the newer ones (like gcc 13 and above) all accept it, using any C++ version (that supports static_assert).
@TedLyngmo I did not realize that change was applied retroactively to existing standards, thanks for the clarification
|
1

Another possible approach would be to consider std::variant for the value parameter, and to use an implementation of std::overload for the visitor:

template <typename... Fs>
struct overload : Fs... {
    using Fs::operator()...;
};

template <typename... Fs>
overload(Fs...) -> overload<Fs...>;

using value_type = std::variant<bool, std::string, int, float>;

void add_property_immutable(std::string name, value_type value) {
    using handle_type = std::variant<
        v8::Handle<v8::Boolean>,
        v8::Local<v8::String>,
        v8::Local<v8::Integer>,
        v8::Local<v8::Number>>;

    auto handle = std::visit<handle_type>(overload{
        [](bool alt) { return v8::Boolean::New(isolate, alt); },
        [](std::string alt) {
            return slim::utilities::StringToV8String(isolate, alt);
        },
        [](int alt) { return v8::Integer::New(isolate, alt); },
        [](float alt) { return v8::Number::New(isolate, alt); },
    }, std::move(value));

    std::visit(handle, []<typename T>(T &&alt) {
        plugin_template->Set(
            slim::utilities::StringToV8String(isolate, name)
            std::forward<T>(alt),
            v8::ReadOnly);
    }, std::move(handle));
}

1 Comment

Thank you. This looks like a fun pattern.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.