1

On this subject, I have read few relevant SO questions/answers/comments. Found only one relevant but somewhat buried question/answer here. Allow me to try and clearly show the issue in question/answer manner. For the benefit of others.

Let the code speak. imagine you design this template.

// value holder V1.0
// T must not be reference or array or both
template<typename T> struct no_arrf_naive
{
    static_assert(!std::is_reference_v<T>, "\n\nNo references!\n\n");
    static_assert(!std::is_array_v<T>, "\n\nNo arrays!\n\n");

    using value_type = T;
    T value;
};

Simple and safe, one might think. Some time after, other folks take this complex large API, where this is buried deep, and start using it. The struct above is deep inside. As usually, they just use it, without looking into the code behind.

        using arf = int(&)[3];

        using naivete = no_arrf_naive<arf>;

        // the "test" works
        constexpr bool is_ok_type = std::is_class_v< naivete >;

    // this declaration will also "work"
    void important ( naivete ) ;

But. Instantiations do not work

    naivete no_compile;

static assert message does show all of a sudden. But how has the "test" compiled and passed? What is going on here?

1 Answer 1

1

The issue is that API is wrong. static_assert as class member does "kick-in" but not before instantiation.

First the offending API commented

    template<typename T>
struct no_arrf_naive
{
    // member declarations
    // used  only on implicit instantiation
    // https://en.cppreference.com/w/cpp/language/class_template#Implicit_instantiation
    static_assert(!std::is_reference_v<T>, "\n\nNo references!\n\n");
    static_assert(!std::is_array_v<T>, "\n\nNo arrays!\n\n");

    using value_type = T;
    T value;
};

Users are here properly coding to transform from Template to Type, but, static_assert's do not kick-in:

    using naivete = no_arrf_naive<arf>;

This might most worryingly go on unnoticed, until someone wants to use this. That will not compile and the message, API author has placed in there, will show at last. But alas, too late.

And on projects laboring on some large C++ source, problems that show up late, are the most notorious ones.

The solution is good old SFINAE. The API fixed is this:

 // value holder
 // references or arrays or both are excluded at compile time
    template<typename T,
    std::enable_if_t< 
      (!std::is_reference_v<T> && !std::is_array_v<T>), bool> = true
     > struct no_arrf
{
    using value_type = T;
    T value;
};

The above will not compile immediately upon trying to create the type from template with either reference or array or both:

  // reference to array of three int's
  using arf = int(&)[3] ;
  // no can do
  using no_naivete = no_arrf<arf>;

  (MSVC) error C2972: 'no_arrf':
  template parameter 'unnamed-parameter': 
  the type of non-type argument is invalid

I might think this whole story might look like trivial or even useless to some. But, I am sure many good folks are coming to SO for badly needed standard C++ advice. For them, this is neither trivial nor useless.

Many thanks for reading.

2
  • Another possibility is a no_arrf_helper template which contains the static_asserts and a nested struct no_arrf_impl, then declare no_arrf like this : template<typename T> using no_arrf = typename no_arrf_helper<T>::no_arrf_impl;
    – Annyo
    Commented Mar 6, 2019 at 13:57
  • "static_assert as class member does "kick-in" but not before instantiation" Not if it doesn't depend on template parameters though, but that doesn't help here. Commented Mar 6, 2019 at 15:39

Not the answer you're looking for? Browse other questions tagged or ask your own question.