Uploaded image for project: 'Rules Repository'
  1. Rules Repository
  2. RSPEC-6000

"std::visit" should be used to switch on the type of the current value in a "std::variant"

    XMLWordPrintable

    Details

    • Type: Code Smell Detection
    • Status: Active
    • Resolution: Unresolved
    • Default Severity:
      Major
    • Impact:
      Low
    • Likelihood:
      High
    • Default Quality Profiles:
      Sonar way, MISRA C++ 2008 recommended
    • Covered Languages:
      C++
    • Remediation Function:
      Constant/Issue
    • Constant Cost:
      30min
    • Analysis Scope:
      Main Sources, Test Sources

      Description

      std::variant is a type-safe union that can hold values of a type out of a fixed list of types.

      Depending on the current alternative inside a variant, it is common to execute dedicated code. There are basically two ways to achieve that:

      • Writing code that checks the current alternative, then getting it and running specific code
      • Letting std::visit perform the check and select the code to run by using overload resolution with the different alternatives

      The second option is usually preferable:

      • It requires less boilerplate code.
      • It is easy to handle multiple similar alternatives together if desired.
      • It is usually more robust: if a new alternative is added to the variant, but the visitor does not support it, it will not compile.

      This rule raises an issue when variant::index is called, or when variant::holds_alternative or variant::get_if is used in a series of if - else if (calling one of these functions in isolation can be an acceptable lightweight alternative to std::visit in some cases).

      Note: When defining the visitor of a variant, it can be nicer to use a series of lambdas by making use of the overloaded pattern

      Noncompliant Code Example

      using Variant = std::variant<int, float, string>;
      void printType1(Variant const &v) {
          switch(v.index()) { // Noncompliant
              case 0: cout << "int " <<get<int>(v) << "\n"; break;
              case 1: cout << "float " <<get<float>(v) << "\n"; break;
              case 2: cout << "string " <<get<string>(v) << "\n";break;
          }
      }
      void printType2(Variant const &v) {
          if(auto p = get_if<int>(&v)) { // Noncompliant
              cout << "int " << *p << "\n";
          } else if (auto p = get_if<float>(&v)) {
              cout << "float " << *p << "\n";
          } else if (auto p = get_if<string>(&v)) {
              cout << "string " << *p << "\n";
          }
      }
      

      Compliant Solution

      using Variant = std::variant<int, float, string>;
      
      struct VariantPrinter {
          void operator() (int i) { cout << "int " << i << "\n"; }
          void operator() (float f) { cout << "float " << f << "\n"; }
          void operator() (std::string const &s) { cout << "string " << s << "\n"; }
      };
      
      void printType3(Variant const &v) {
          std::visit(VariantPrinter{}, v);
      }
      
      // Same principle, but using the overloaded pattern
      void printType4(Variant const &v) {
          std::visit(overloaded{
              [](int i){cout << "int " << i << "\n";},
              [](float f){cout << "float " << f << "\n";},
              [](std::string const &s){cout << "string " << s << "\n";}
          }, v);
      }
      

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              Unassigned Unassigned
              Reporter:
              loic.joly Loïc Joly
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Dates

                Created:
                Updated: