0

I am writing a library where object parameters that are inherently static are built into the type using non-type template parameters. Doing this offers massive performance optimization over an implementation where these values are runtime values (small benchmarks measured at 10x, expected 5-7x). However, I find that C++ does not support this idiom very well. I am trying to refactor the library to make it easier to use by developers that aren't very familiar with templates or metaprogramming.

I would like to offer constexpr functions that simplify the extraction of static constexpr member variables (that are used to propogate the static values through the types), so that users do some basic metaprogramming without understanding the heavy machinery behind the scenes. For example,

#include <type_traits>


template <int A, int B>
class Example {
public:
    static constexpr auto C = A + B;
private:
    int b {0};
};

template <int A, int B>
constexpr auto getC(Example<A, B> e) { return decltype(e)::C; }

/**** USER CODE ****/

int main()
{
    Example<1, 2> e;
    Example<2, getC(e)> f;
}

This does not work, e being non-constexpr can be used in a constexpr context, even if you aren't using the value (strangely if Example had no runtime members, it would work). One could write decltype(e)::C instead, but if it's a reference they would have to std::remove_reference<decltype(C)>::type::C and of course they would have to know to do this, and know enough to get around common problems like the remove_reference call. And at that point we are off in the weeds for the typical programmer this library is meant for.

constexpr functions don't work, macros don't work with the type system so they are also insufficient, how else could I accomplish this?

2 Answers 2

1

You can use a macro to wrap

std::remove_reference<decltype(C)>::type::C

That would look like

#define getC(obj) std::remove_reference_t<decltype(obj)>::C
Sign up to request clarification or add additional context in comments.

1 Comment

I had considered this quickly, but I didn't like that it doesn't do much in the way of checking types, so you tend to get crappy error messages. The answer that was deleted was better in that respect, but the invocation was messy. So I chose both. Thank you for making me reconsider.
1

I chose a combination of Nathan's answer, and an answer that was deleted, for the implementation of getC.

template <typename T, typename = std::void_t<decltype(std::remove_reference_t<T>::C)>>
constexpr int getC_() { return std::remove_reference_t<T>::C; }

#define getC(e) (getC_<decltype(e)>())

The function does the heavy lifting and gives a good error message if you give it an invalid type thanks to the guard. And the macro does the messy (to a beginner) invocation in a way that can never fail on it's own.

getC(1);  // error: 'C' is not a member of 'std::remove_reference<int>::type' {aka 'int'}

1 Comment

Nice solution. You can even switch the SFINAE out for a static assert in the function body if you want to give a custom error message

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.