ktbarrett
ktbarrett

Reputation: 1595

Using static constexpr member variables of a non-constexpr object as template arguments

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?

Upvotes: 0

Views: 138

Answers (2)

ktbarrett
ktbarrett

Reputation: 1595

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'}

Upvotes: 1

NathanOliver
NathanOliver

Reputation: 180415

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

Upvotes: 1

Related Questions