3

I have a problem with MSVC 2017 & 2019 about this limitation: I would like to know at compile time if a certain CRTP template "derived" class have a specific function.

I got this as my class trait evaluation :

template <typename T>
class function_exists_trait
{
private:
    template <typename U>
    static auto test_todo(U * u) -> decltype(&U::exists) {}
    static auto test_todo(...) -> std::false_type {}

public:
    static constexpr bool value{ !std::is_same<decltype(test_todo(static_cast<T*>(nullptr))), std::false_type>::value };
};

I tested it with some trivial classes :

struct t1 { void exists() {} };
struct t2 { void exists() {} };
struct t3 {};
struct t4 : public t1 {};
struct t5 : public t3 {};
struct t6 : public t3 { void exists() {} };

I got the expected results. As expected, the evaluations give : 1 1 0 1 0 1 with this test : cout << function_exists_trait<t1>::value << " " << ...

I got the expected results for the following CRTP simple implementations (0 1 1 1) :

template <typename t> struct u1 {};
struct crtp1 : public u1<crtp1> { void exists() {} };
template <typename t> struct u2 { void exists() {} };
struct crtp2 : public u2<crtp2> {};

cout << function_exists_trait<u1<int>>::value << " "
     << function_exists_trait<crtp1>::value << " "
     << function_exists_trait<u2<int>>::value << " "
     << function_exists_trait<crtp2>::value << endl;

The problem is this : when a try to evaluate the trait inside the CRTP base class, nothing is working and I do not understand why.

template <typename t> struct u3 { 
    static inline constexpr bool value{ function_exists_trait<t>::value }; 
};
struct crtp3 : public u3<crtp3> { void exists() {} };

template <typename t> struct u4 { 
    void exists() {}
    static inline constexpr bool value{ function_exists_trait<t>::value };
};
struct crtp4 : public u4<crtp4> {};

template <typename t> struct u5 {
    void exists() {}
    static inline constexpr bool value{ function_exists_trait<t>::value };
};
struct crtp5 : public u5<crtp5> {
    void exists() {}
};

The following code give this result : 0 0 - 0 0 - 0 0 - 0 0

cout << function_exists_trait<u3<int>>::value << " " << u3<int>::value << " - "
        << function_exists_trait<crtp3>::value << " " << crtp3::value << " - " 
        << function_exists_trait<crtp4>::value << " " << crtp4::value << " - "
        << function_exists_trait<crtp5>::value << " " << crtp5::value << endl;

I have posted this problem thinking I was doing something wrong but it seem to be a problem with MSVC. Thanks to P.W. who showed me that it seem to be a limitation of MSVC. He showed me that exact same code give this result under gcc : 0 0 - 1 1 - 1 1 - 1 1

Do anybody have any suggestion how to solve this. Maybe there is an obvious solution with MSVC to trigger the expected result. Or simply another way to achieve the same goal under MSVC.

Aesope
  • 445
  • 6
  • 15
  • Please post a [mcve] in your question so that we can help you. – Richard Critten May 21 '19 at 18:34
  • I'm sorry, is the following link is sufficient as minimal, complete et verifiable example ( https://stackoverflow.com/questions/56231762/c-trait-for-function-existence-is-not-working-with-crtp ). The current post is a sequel of the linked one. Please tell me if it is sufficient. Thanks! – Aesope May 22 '19 at 02:21
  • @Aesope questions on SO need to be self-contained, i.e. contain enough information to be answered in their own right without requiring access to another question. Its fine to link to existing questions - so long as its not essential to read them to solve this question. – Dale K May 22 '19 at 03:21
  • I understand. Thanks for the advices! :) – Aesope May 22 '19 at 04:46
  • 1
    [This answer](https://stackoverflow.com/q/37816186/4326278) explains what's happening, and also provides a workaround (use a function). It looks like MSVC hasn't implemented the new C++17 rules yet. You're presumably targeting C++17 (since you're using inline variables), but it's probably worth noting that MSVC's behaviour is correct in C++14. Because of this change between standard versions, I think using a function instead of a data member is safer and more portable. – bogdan May 22 '19 at 22:02
  • Thank you so much bogdan. Your link explain well the problem and a workaround. Also, I would like to know what are you meaning by "using a function instead of a data member is safer and more portable"? – Aesope May 23 '19 at 13:36
  • @Aesope I meant it will behave the same way across compilers and versions. For static data members, only considering the latest compiler versions so far, we've got Clang that implements the correct (different) C++14 and C++17 behaviours depending on the standard mode you set, then there's GCC that seems to have been implementing the C++17 behaviour all along, and MSVC and EDG that seem to only have the C++14 behaviour implemented so far, even in C++17 mode. Member function instantiation, on the other hand, works the same way across all four of them, and has been like that for a long time. – bogdan May 23 '19 at 14:28
  • To clarify further, I didn't mean that as a general statement about data members versus functions; I'm not a fan of general statements like "make everything a function" or anything like that :-). I was referring specifically to this case of static data members that have an initializer specified in the class definition, used in a context where the point of instantiation is of critical importance. – bogdan May 23 '19 at 14:41
  • @bogdan I understand your points. Also, thanks to point out and taking the time to explain the different implementations on compilers. Those are hard to know from a beginner point of view. – Aesope May 24 '19 at 14:10

1 Answers1

1

As bogdan found, this is a working alternative :

template <typename t> struct u6 {
    void exists() {}
    static constexpr bool value() { return function_exists_trait<t>::value; }
};
struct crtp6 : public u6<crtp6> {
    void exists() {}
};

Thanks to all!

Aesope
  • 445
  • 6
  • 15