3

Guillaume Racicot gave an excellent answer to this question on how I could specialize template variables. But I'm having trouble in with creating a templated array of function pointers. This code for example:

struct vec
{
    double x;
    double y;
    double z;
};

namespace details
{
template <typename T>
constexpr double X(const T& param) { return param.x; }

template <typename T>
constexpr double Y(const T& param) { return param.y; }

template <typename T>
constexpr double Z(const T& param) { return param.z; }
}

template <typename T, typename = void>
constexpr double (*my_temp[])(const vec&) = { &details::X<T>, &details::Y<T> };

template <typename T>
constexpr double (*my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>>>[])(const vec&) = { &details::X<T>, &details::Y<T>, &details::Z<T> };


int main() {
    vec foo = { 1.0, 2.0, 3.0 };

    for(const auto i : my_temp<decltype(foo)>) {
        cout << (*i)(foo) << endl;
    }
}

In gcc outputs:

1
2
3

But in only outputs:

1
2

Is there something I can do to work around this?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

1 Answers1

1

Welcome to the world of compiler bugs! Your syntax is completely valid, yet only GCC can compile it.

So far, I tested with multiple clang, gcc and msvc versions.

Your variation with the function pointer raw array, only GCC parses it correctly. Clang 8.0.0 will crash, and MSCV will not compile it.

I tried two other variations: with a template alias and std::array

Function pointer alias template:

template<typename T>
using fptr = auto(*)(T const&) -> double;

template <typename T, typename = void>
constexpr fptr<T> my_temp[] = {
    &details::X<T>, &details::Y<T>
};

template <typename T>
constexpr fptr<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>>>[] = {
    &details::X<T>, &details::Y<T>, &details::Z<T>
};

std::array + CTAD:

template <typename T, typename = void>
constexpr std::array my_temp = {
    &details::X<T>, &details::Y<T>
};

template <typename T>
constexpr std::array my_temp<T, enable_if_t<is_floating_point<decltype(details::X(T()))>::value>> = {
    &details::X<T>, &details::Y<T>, &details::Z<T>
};

To remove CTAD, simply use std::array<auto(*)(const vec&) -> double, 3>.

Here's the results:

+------------+-------+-------+-------+
| Compiling? |  GCC  | Clang | MSVC  |
+------------+-------+-------+-------+
| raw array  |  Yes  |  ICE  |  No   |
+------------+-------+-------+-------+
| fptr alias |  Yes  |  ICE  |  Yes  |
+------------+-------+-------+-------+
| std::array |  Yes  |  Yes  |  Yes  |
+------------+-------+-------+-------+

Note that on the upcoming clang 9, it will be on par with GCC. All versions needs at least MSVC 2017. With workaround I'm sure it's possible to make it work with msvc 2015 too.

In the end, as long as it work on the platform you need right now, that would be okay. std::array have a small compile time cost, but the raw array is surprisingly less portable as of right now.

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • 1
    [clang 8 appears to work](https://wandbox.org/permlink/ud26H0CPIy7PE6eR) – AndyG Jun 18 '19 at 13:25
  • No? Haha, I don't know which to believe. – AndyG Jun 18 '19 at 14:00
  • Me neither at this point. I don't have clang installed on the computer I use right now, only AppleClang. – Guillaume Racicot Jun 18 '19 at 14:01
  • @AndyG and Guillaume Racicot OK guys one more: I understand what we're doing here, and I can use the workarounds, but I'm not following why the specialized argument must always be `void` for this to work. Can you guys help clarify for me: https://stackoverflow.com/q/56667847/2642059 – Jonathan Mee Jun 19 '19 at 12:41
  • 1
    @JonathanMee: Max's answer there is spot on. You might be able to write yet another level of indirection if you choose not to provide `void` – AndyG Jun 19 '19 at 12:55