1

Yesterday it took me ages to come to grips with a compile-time error caused by calling a const member function from a non-const object as in this example:

// my utility header template<typename derived> struct crtp_task : task { std::list<task*> list; // note: cannot be const, as task::allocate_child() isn't template<typename... Args> void add_child(Args&&... args) { list.push_back(new(task::allocate_child()) derived(std::forward<Args>(args)...)); } /* ... */ }; // an application struct my_task : crtp_task<my_task> { some_data data; /* ... */ my_task(some_data d) data(d) {} void generate_children() const // constant member { /* ... */ some_data child_data = /* ... */; this->add_child(child_data); // error: cannot call non-const member } }; 

The clang error message was several lines and too cryptic (not mentioning const), but gcc came up with a better error (though even more lines, but eventually complaining about me ignoring cv qualifiers).

So, to avoid this sort of thing in the future, I thought about using static_assert() in my utility header. My naive approach

// my utility header template<typename derived> struct crtp_task : task { std::list<task*> list; // note: cannot be const, as task::allocate_child() isn't template<typename... Args> void add_child(Args&&... args) { list.push_back(new(task::allocate_child()) derived(std::forward<Args>(args)...)); } // note: catch call from const objects template<typename... Args> void add_child(Args&&...) const { static_assert(false,"add_child() const called"); } /* ... */ }; 

fails, as the compiler immediately triggers an error, even if the template void add_child() const is never called. How else can I make this work?

3
  • 8
    I'm not entirely sure what you want. Wouldn't template<typename... Args> void add_child(Args&&...) const = delete; be enough? Commented Apr 19, 2013 at 9:25
  • Sounds like a Quality Of Implementation issue TBH. The problem with the current error messages is that they fail to eliminate irrelevant details. Compiler writes: when it really doesn't matter what Args... is, leave it out. IDE writes: make those irrelevant lines of output hidden by default. Commented Apr 19, 2013 at 13:18
  • @R.MartinhoFernandes yes, I suppose. As you comment was before PlasmaHH's answer, I'd accept an answer by you of the same content as your comment. Commented Apr 22, 2013 at 16:08

2 Answers 2

3

If you want some overload to cause a compilation error when it gets selected in overload resolution, you can use the new = delete feature.

template<typename... Args> void add_child(Args&&...) const = delete; 

This will generate a nice simple error along the lines of "use of deleted function void add_child(Args&&...) const".

Sign up to request clarification or add additional context in comments.

Comments

3

static_assert must depend on a template parameter to be delayed after the instantiation of the template. Otherwise they will be "called" as soon as all information is available, in your case the false is ready pretty early in compilation.

To solve this, I usually make it "artificially" depend on a template parameter just like in:

template<class FOO> void foo( const FOO& ) { static_assert(sizeof(FOO)==0,"This function should never be instantiated"); } 

Maybe in your case just deleting the function might be the better approach though.

2 Comments

static_assert( sizeof...(Args) == -1, "chicken!") technically depends on your arguments, but will always fail once instantiated. (unless you go around calling the function with at least 4,294,967,295 arguments, or are writing code on a 16 bit system where a mere 65,535 arguments.
sizeof(FOO) != sizeof(FOO) is even more foolproof.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.