Achieving a good compile-time error msg. based on value of parameter to factory function

116 Views Asked by At

I'm implementing a constant/immutable class in C++ 20 and I'd like to make as much as possible constant and do some value checks in my factory functions.

Below is a simplified example (paste to godbolt.org) of what I'm trying to achieve. Please see the code comments on the options I'm considering.

Note that this is for an embedded project and using exceptions is not an option.

Any ideas on achieving good compile-time errors for bad values of "i" where the error msg. actually relates to to the value of i (without templates)?

Update We actually have some non constant uses of this class (where "i" is not constant) so it seems that consteval is out of the picture (together with the template case). Basically, the question is then: Is it possible to have compile-time errors if "i" is constant and runtime errors if "i" is not?

Update2 Updated code with option 5, based on comments below.

#include <cstdlib>

#define Assert while(true) {}

class ConstantClass
{
    public:

        static consteval ConstantClass ConstEvalFactory(int i)
        {
            if(i < 0)
            {
                // Option 1
                // - Use #error
                // - Always fails at compile time
                // - even for valid i

                // #error "Invalid value"

                // Option 2
                // - Non constant expression inside consteval
                // - Compile-time error msg. do not relate to the value of i

                Assert(false);
            }
            else
            {
                return ConstantClass(i);
            }
        }

        template<int i>
        static consteval ConstantClass TemplateConstEvalFactory() 
        {
            // Option 5
            // - template function with static assert
            // - user will have to choose which factory to call
            //   - I.e. x<...>() or x(...)
            static_assert(i>=0);

            return ConstantClass(i);
        }

        static constexpr ConstantClass ConstExprFactory(int i) 
        {
            if(i < 0)
            {
                // Option 3
                // - Non constant expression inside constexpr
                // - If i < 0, this will compile to non-constant and fail at runtime
                // - Failure will depend on implementation of "Assert"

                Assert(false);
            }
            else
            {
                return ConstantClass(i);
            }
        }

    private:
        constexpr ConstantClass(int i)
        {
            // Construction implementation
        }
};

template<int i>
class ConstantTemplateClass
{
    // Option 4
    // - static assert on template parameter
    // - Works but we'd prefer non templates in this case

    static_assert(i>=0);
};

int main()
{
    // Examples of invalid instances
    // ConstantClass MyC = ConstantClass::ConstEvalFactory(-1);
    // ConstantClass MyC = ConstantClass::TemplateConstEvalFactory<-1>();
    // ConstantClass MyC = ConstantClass::ConstExprFactory(-1);
    // ConstantTemplateClass<-1> MyC;
}
1

There are 1 best solutions below

2
max66 On

Following my comment, I suggest a double-factory; a consteval one, where the argument is a template parameter and can be checked with a static_assert(), and another for the run-time cases where the argument can be checked run time.

The following is compiling (commenting the a2 declaration) example

#include <iostream>

class NotEverConstClass
{
  public: 
    template <int I>
    static consteval NotEverConstClass constFactory () 
    {
      static_assert( I >= 0, "some explicative error message 1" );

      return NotEverConstClass{I};
    }

    static NotEverConstClass runTimeFactory (int i)
    {
      if ( i < 0 )
        std::cout << "some explicative error message 2" << std::endl;

      return NotEverConstClass{i};
    }

  private:
    constexpr NotEverConstClass (int) 
    { }
};

int main()
{
  // compilation OK, runtime OK
  constexpr auto a0 { NotEverConstClass::constFactory<42>() };

  // compilation OK, runtime OK
  auto a1 { NotEverConstClass::runTimeFactory(42) };

  // compilation error with message "some explicative error message 1"
  // constexpr auto a2 { NotEverConstClass::constFactory<-42>() };
 
  // compilation OK, runtime error "some explicative error message 2" 
  auto a3 { NotEverConstClass::runTimeFactory(-42) };
}