Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
9-3-3 and interfaces enforced via templates instead of virtual functions
#1
Hi,

We are having trouble following Rule M9-3-3 when implementing interfaces using templates, similarly to the idea of "named requirements". I have read a similar post about this rule here and it's stated that the rule will be revised to also take the "override set" of virtual functions into account, making sure a function should be made const only if the whole "override set" can be made const.

Sometimes it might not be feasible to implement interfaces via virtual functions, and templates can be used to achieve the same result instead:

Code:
template <typename FooT>
void foo(FooT& f)
{
   f.run();
}


This pattern is commonly used e.g. in static dependency injection. We can pass any type `FooT` as long as it fulfills the interface `FooT::run`. For some types, `run` could be made const, but not all of them, therefore we get warnings from our static analyzers in such functions.

Will you consider this use case in the next revision of the rule? How do you suggest we work with it?

Thanks!
Reply
#2
We're not sure what the problem is that you are concerned about.

If you have class A with  void run() const;  and  class B with  void run();  and the two versions of run satisfy 9-3-3  (i.e. A::run() can be const, but B::run() can't be const), then you can instantiate foo with both A and B.

We can see that you may get a violation of 7-1-2 (make reference parameters const where possibly), but that can be avoided by overloading foo with void foo(const FooT& f)

Please clarify the concern
Posted by and on behalf of
the MISRA C++ Working Group
Reply
#3
Thanks for the reply. I realize how my example was unclear, let me show a couple more examples.

I think the main confusion with this rule is that "can be const" is ambiguous. What does it mean "can"?

Example 1: I want to implement a STL-like container class. To be consistent with what STL expects ("named requirements"), I need to implement:

Code:
T* data()       { return data_; }  // Violating M9-3-3?
const T* data() const { return data_; }

These two functions are identical and they do not modify the internal state of the class. STL expects all container classes to have these 2 functions for read and write access. However, technically, one "can" add const to the first one, because the function is not modifying the internal state. It's just creating a copy of the internal pointer and returning it. The compiler will accept const and static analyzers will require const. However we will not be able to use this class because it doesn't fulfill the required expectations.

Example 2: this is a slightly off-topic from "interfaces enforced via templates" but still relevant I think; let me know if you'd rather have it in a separate post.

Code:
class Foo
{
public:
    ~Foo() { delete x_; }

    void set(int x)  // Violates M9-3-3? Technically, I "can" add const and the code will compile
    {
       *x_ = x;
    }
    
private:
    int* x_{new int};
};

Here we have a setter, which everyone expects to be "non-const" as it's setting the internal state of the class. However, since it's a pointer, the compiler will accept adding "const". Technically, I "can" add const. But that doesn't mean I "should". Adding const will be against the principle of least surprise and will confuse developers. I'm aware of std::experimental::propagate_const, but naturally we are not going to use experimental features in a safety-critical environment.

Personally, I think it would be good to rephrase this rule in terms of "semantic const", instead of "syntactic const". Or at least clarify precisely what it's meant by "can be const".

Looking forward to your thoughts!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)