0

I would want to use static_assert to enforce various limitations on configuration of my class. Earlier I would just use an enum and only allow a single constructor which requires said enum to enforce a limit on my class. This worked fine if I have something like below and the range is from 0 to 4, but once I have a range of 0 to 500 then using an enum becomes unwieldy.

Some_Class.h

class Some_Class {
    public:
        Some_Class(const unsigned int param);
    private:
        const unsigned int member_param;
};

Some_Class.cpp

Some_Class::Some_Class(const unsigned int param) : member_param(param) {
    static_assert(member_param < 500, "Param must be less than 500.");
};

Main.cpp

Some_Class foo4(125); // OK
Some_Class foo5(500); // Should fail at compile time.

This is what GCC throws at me when compiling with C++14:

1>  Some_Class.cpp: In constructor 'Some_Class::Some_Class(unsigned int)':
1>C:\some_path\Some_Class.cpp(3,2): error : non-constant condition for static assertion
1>    static_assert(member_param < 500, "Param must be less than 500.");
1>    ^
1>C:\some_path\Some_Class.cpp(3,2): error : use of 'this' in a constant expression
hak8or
  • 488
  • 2
  • 9
  • 28
  • 1
    What if someone does `Some_Class foo6(getCurrentTimeInMilliseconds() % 1000);`? – user253751 Nov 26 '15 at 04:13
  • 2
    Static asserts are performed at compile time. Function parameters are passed at runtime. I suggest a regular assert. – Neil Kirk Nov 26 '15 at 04:15
  • Ah, dang, I just realized. Is there any way to enforce a function parameter check for constructors at compile time, or does that actually just warrant a new question? Maybe throwing constexpr at this somehow might help. – hak8or Nov 26 '15 at 04:16
  • @NeilKirk I am on a relatively minimal embedded system, so the ~4KB worth of code that assert adds even with optimizations is too much for my environment. I was hoping to use static_assert since based on my understanding it should introduce minimal if not zero extra code size. – hak8or Nov 26 '15 at 06:07
  • What? Asserts are usually disabled in release builds. Where does 4kb come from? – Neil Kirk Nov 26 '15 at 12:33
  • You are right, but to actually use assert when debugging I still need to fit 4KB more worth of code on my device, and I only have less than 2 KB left worth of flash. – hak8or Nov 26 '15 at 18:13
  • You can create your own assert macro that just calls abort. – Neil Kirk Nov 27 '15 at 01:32

2 Answers2

2

parameter value cannot be used in constexpr.

you have to turn in compile time value somehow:

  • template your whole class:

    template<unsigned int size>
    class Some_Class {
        static_assert(size < 500, "Size should be less than 500");
    public:
        constexpr unsigned int member_param = size;
    };
    
  • pass a integral_constant:

    template <unsigned int N>
    using uint_c = std::integral_constant<unsigned int, N>;
    
    class Some_Class {
    public:
        template<unsigned int size>
        Some_Class(uint_c<size>) : member_param(size)
        {
            static_assert(size < 500, "Size should be less than 500");
        }
    private:
        unsigned int member_param;
    };
    
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

If you want compile time check you should use templates:

template<int size>
class Some_Class {
public:
    Some_Class() : member_param(size) { static_assert(size < 500, "Size should be less than 500"); }
private:
    const unsigned int member_param;
}

Then using the class the user must specify the size explicitly:

void myFunction() {
    Some_Class<20> class20; // Works
    Some_Class<500> class500; // Compiler error
}

Edit: Based on the comment from @NeilKirk the following code will achive the same without having to make the whole class a template:

class Some_Class {
public:
    template<int size>
    static Some_Class* createClass() {
        static_assert(size < 500, "Size should be less than 500");
        return new Some_Class(size);
    }
private:
    Some_Class(int size) : member_param(size) { assert(size < 500); }
private:
    const unsigned int member_param;
};

What it does: The constructor takes in the size but is private. Thus the class must be created using the createClass static function that is a template and you can do a static_assert on the size.

To create an object:

Some_Class* class = Some_Class::createClass<20>();

The normal assert is added in the Constructor for in-case if someone derives from the class and make the constructor public.

CJCombrink
  • 3,738
  • 1
  • 22
  • 38
  • Why not just make the constructor a template? – Neil Kirk Nov 27 '15 at 01:33
  • @NeilKirk That is an interesting idea, but after a quick google search and writing a bit of code I could not seem to make it work for the constructor to be a template. I am adding an alternative to my original answer based on your comment. – CJCombrink Nov 27 '15 at 05:41