0

I know this type of errors occur when a base class is only forward declared, but in my case it's fully implemented as far as I can tell:

I'm trying to create a unit system which would only compile if the correct units are used, employing literals and algebraic operators.

I start with a base class Units which is just a wrapper over T, and is inherited by all the other units.

Then I define the allowed algebraic operators, which should return the correct units.

I get

error: invalid use of incomplete type ‘class Units<T>’
[build]   107 |     return Mps{static_cast<T>(rhs) / static_cast<T>(lhs)};

for this code:

template<typename T>
class Units
{
protected:
    T val;
public:
    constexpr explicit Units(T val) : val(val) { }
    constexpr explicit operator T&() { return val; }
    constexpr explicit operator T() const { return val; }
    constexpr auto operator<=>(const Units<T> rhs) {
        return val <=> rhs.val; 
    }
    constexpr bool operator==(const Units<T> rhs) const { return val == rhs.val; }

};


template<typename T>
class Meters : public Units<T>
{
    using typename Units<T>::Units;
};

template<typename T>
class Seconds : public Units<T>
{
    using typename Units<T>::Units;
};

template<typename T>
class Mps : public Units<T>
{
    using typename Units<T>::Units; 
};


constexpr Meters<long double> operator "" _km(long double km) {
    return Meters<long double>{1000 * km};
}


constexpr Seconds<long double> operator "" _s(long double s) {
    return Seconds<long double>{s};
}


constexpr Mps<long double> operator "" _mps(long double s) {
    return Mps<long double>{s};
}


template<typename T>
constexpr Mps<T> operator / (const Meters<T> &&rhs, const Seconds<T> &&lhs) {
    return Mps{static_cast<T>(rhs) / static_cast<T>(lhs)};
}


int main() {
    return 1_km / 2_s == 500_mps
}
feature_engineer
  • 1,088
  • 8
  • 16
  • 2
    `using typename Units::Units;` -> `using Units::Units;`. – Jarod42 Sep 13 '21 at 08:32
  • @Jarod42 With your recommended fix and some others, I got it running: [Demo on coliru](http://coliru.stacked-crooked.com/a/47631717d24e3033) – Scheff's Cat Sep 13 '21 at 08:36
  • 1
    What compiler are you using? I an getting completely different errors. – n. m. could be an AI Sep 13 '21 at 08:38
  • @Jarod42 using typename Units::Units is actually Ok (https://stackoverflow.com/questions/25940365/what-is-the-standard-conform-syntax-for-template-constructor-inheritance), what solved it was explicit template parameter in the return statement, as mentioned in the answer below. – feature_engineer Sep 13 '21 at 09:02
  • @feature_engineer: With minimal reproducible code, we were able to copy-paste into online compiler, then, we have report from different compilers (some are more verbose than other, or more relevant). Useful to solve your issue yourself (if you don't have several compiler locally). – Jarod42 Sep 13 '21 at 09:10
  • 1
    Having it in an accepted SO answer *and* having gcc accept it doesn't make it Ok. [If the terminal name of the using-declarator is dependent (\[temp.dep.type\]), the using-declarator is considered to name a constructor if and only if the nested-name-specifier has a terminal name that is the same as the unqualified-id](https://eel.is/c++draft/namespace.udecl#1). So `Units::Units` names a constructor, not a type, and adding `typename` does't make it magically not so. Clang rejects this construct. – n. m. could be an AI Sep 13 '21 at 10:44

1 Answers1

2

Looks like the compiler is confused and the warning is misleading. Provide the missing template argument to fix it:

return Mps<T>{static_cast<T>(rhs) / static_cast<T>(lhs)};
          ^^^

You can (probably) define a deduction guide if you wish to avoid specifying the argument explicitly.


Other issues:

  • Missing semicolon.
  • Your operators are for floating point, but you use integer in 1_km; that won't work. Either use floating point literal, or add an overload for integers.
  • using typename Units<T>::Units; is wrong. Lose the typename.
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Actually, the problem was just the missing template argument (and the missing semi-colon in my usage example code). The typename after using is Ok. I was using it because of https://stackoverflow.com/questions/25940365/what-is-the-standard-conform-syntax-for-template-constructor-inheritance – feature_engineer Sep 13 '21 at 09:00
  • @feature_engineer Interesting. Clang won't accept it. – eerorika Sep 13 '21 at 09:22
  • And you can see [how-to-make-argument-deduction-work-for-derived-class-which-using-base-class-con](https://stackoverflow.com/questions/61311297/how-to-make-argument-deduction-work-for-derived-class-which-using-base-class-con) for why CTAD doesn't apply here. – Jarod42 Sep 13 '21 at 12:35