/** * @file cppfeatures_test * @author Vir * @date 2021-03 * @brief cpp features * * $LicenseInfo:firstyear=2021&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2021, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ // Tests related to newer C++ features, for verifying support across compilers and platforms #include "linden_common.h" #include "../test/lltut.h" namespace tut { struct cpp_features_test {}; typedef test_group cpp_features_test_t; typedef cpp_features_test_t::object cpp_features_test_object_t; tut::cpp_features_test_t tut_cpp_features_test("LLCPPFeatures"); // bracket initializers // Can initialize containers or values using curly brackets template<> template<> void cpp_features_test_object_t::test<1>() { S32 explicit_val{3}; ensure(explicit_val==3); S32 default_val{}; ensure(default_val==0); std::vector fibs{1,1,2,3,5}; ensure(fibs[4]==5); } // auto // // https://en.cppreference.com/w/cpp/language/auto // // Can use auto in place of a more complex type specification, if the compiler can infer the type template<> template<> void cpp_features_test_object_t::test<2>() { std::vector numbers{3,6,9}; // auto element auto& aval = numbers[1]; ensure("auto element", aval==6); // auto iterator (non-const) auto it = numbers.rbegin(); *it += 1; S32 val = *it; ensure("auto iterator", val==10); } // range for // // https://en.cppreference.com/w/cpp/language/range-for // // Can iterate over containers without explicit iterator template<> template<> void cpp_features_test_object_t::test<3>() { // Traditional iterator for with container // // Problems: // * Have to create a new variable for the iterator, which is unrelated to the problem you're trying to solve. // * Redundant and somewhat fragile. Have to make sure begin() and end() are both from the right container. std::vector numbers{3,6,9}; for (auto it = numbers.begin(); it != numbers.end(); ++it) { auto& n = *it; n *= 2; } ensure("iterator for vector", numbers[2]==18); // Range for with container // // Under the hood, this is doing the same thing as the traditional // for loop above. Still uses begin() and end() but you don't have // to access them directly. std::vector numbersb{3,6,9}; for (auto& n: numbersb) { n *= 2; } ensure("range for vector", numbersb[2]==18); // Range for over a C-style array. // // This is handy because the language determines the range automatically. // Getting this right manually is a little trickier. S32 pows[] = {1,2,4,8,16}; S32 sum{}; for (const auto& v: pows) { sum += v; } ensure("for C-array", sum==31); } // override specifier // // https://en.cppreference.com/w/cpp/language/override // // Specify that a particular class function is an override of a virtual function. // Benefits: // * Makes code somewhat easier to read by showing intent. // * Prevents mistakes where you think something is an override but it doesn't actually match the declaration in the parent class. // Drawbacks: // * Some compilers require that any class using override must use it consistently for all functions. // This makes switching a class to use override a lot more work. class Foo { public: virtual bool is_happy() const = 0; }; class Bar: public Foo { public: bool is_happy() const override { return true; } // Override would fail: non-const declaration doesn't match parent // bool is_happy() override { return true; } // Override would fail: wrong name // bool is_happx() override { return true; } }; template<> template<> void cpp_features_test_object_t::test<4>() { Bar b; ensure("override", b.is_happy()); } // final // // https://en.cppreference.com/w/cpp/language/final: "Specifies that a // virtual function cannot be overridden in a derived class or that a // class cannot be inherited from." class Vehicle { public: virtual bool has_wheels() const = 0; }; class WheeledVehicle: public Vehicle { public: virtual bool has_wheels() const final override { return true; } }; class Bicycle: public WheeledVehicle { public: // Error: can't override final version in WheeledVehicle // virtual bool has_wheels() override const { return true; } }; template<> template<> void cpp_features_test_object_t::test<5>() { Bicycle bi; ensure("final", bi.has_wheels()); } // deleted function declaration // // https://en.cppreference.com/w/cpp/language/function#Deleted_functions // // Typical case: copy constructor doesn't make sense for a particular class, so you want to make // sure the no one tries to copy-construct an instance of the class, and that the // compiler won't generate a copy constructor for you automatically. // Traditional fix is to declare a // copy constructor but never implement it, giving you a link-time error if anyone tries to use it. // Now you can explicitly declare a function to be deleted, which has at least two advantages over // the old way: // * Makes the intention clear // * Creates an error sooner, at compile time class DoNotCopy { public: DoNotCopy() {} DoNotCopy(const DoNotCopy& ref) = delete; }; template<> template<> void cpp_features_test_object_t::test<6>() { DoNotCopy nc; // OK, default constructor //DoNotCopy nc2(nc); // No, can't copy //DoNotCopy nc3 = nc; // No, this also calls copy constructor (even though it looks like an assignment) } // defaulted function declaration // // https://en.cppreference.com/w/cpp/language/function#Function_definition // // What about the complementary case to the deleted function declaration, where you want a copy constructor // and are happy with the default implementation the compiler will make (memberwise copy). // Now you can explicitly declare that too. // Usage: I guess it makes the intent clearer, but otherwise not obviously useful. class DefaultCopyOK { public: DefaultCopyOK(): mVal(123) {} DefaultCopyOK(const DefaultCopyOK&) = default; S32 val() const { return mVal; } private: S32 mVal; }; template<> template<> void cpp_features_test_object_t::test<7>() { DefaultCopyOK d; // OK DefaultCopyOK d2(d); // OK DefaultCopyOK d3 = d; // OK ensure("default copy d", d.val()==123); ensure("default copy d2", d.val()==d2.val()); ensure("default copy d3", d.val()==d3.val()); } // initialize class members inline // // https://en.cppreference.com/w/cpp/language/data_members#Member_initialization // // Default class member values can be set where they are declared, using either brackets or = // It is preferred to skip creating a constructor if all the work can be done by inline initialization: // http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#c45-dont-define-a-default-constructor-that-only-initializes-data-members-use-in-class-member-initializers-instead // class InitInline { public: S32 mFoo{10}; }; class InitInlineWithConstructor { public: // Here mFoo is not specified, so you will get the default value of 10. // mBar is specified, so 25 will override the default value. InitInlineWithConstructor(): mBar(25) {} // Default values set using two different styles, same effect. S32 mFoo{10}; S32 mBar = 20; }; template<> template<> void cpp_features_test_object_t::test<8>() { InitInline ii; ensure("init member inline 1", ii.mFoo==10); InitInlineWithConstructor iici; ensure("init member inline 2", iici.mFoo=10); ensure("init member inline 3", iici.mBar==25); } // constexpr // // https://en.cppreference.com/w/cpp/language/constexpr // // Various things can be computed at compile time, and flagged as constexpr. constexpr S32 compute2() { return 2; } constexpr S32 ce_factorial(S32 n) { if (n<=0) { return 1; } else { return n*ce_factorial(n-1); } } template<> template<> void cpp_features_test_object_t::test<9>() { S32 val = compute2(); ensure("constexpr 1", val==2); // Compile-time factorial. You used to need complex templates to do something this useless. S32 fac5 = ce_factorial(5); ensure("constexpr 2", fac5==120); } // static assert // // https://en.cppreference.com/w/cpp/language/static_assert // // You can add asserts to be checked at compile time. The thing to be checked must be a constexpr. // There are two forms: // * static_assert(expr); // * static_assert(expr, message); // // Currently only the 2-parameter form works on windows. The 1-parameter form needs a flag we don't set. template<> template<> void cpp_features_test_object_t::test<10>() { // static_assert(ce_factorial(6)==720); No, needs a flag we don't currently set. static_assert(ce_factorial(6)==720, "bad factorial"); // OK } // type aliases // // https://en.cppreference.com/w/cpp/language/type_alias // // You can use the "using" statement to create simpler templates that // are aliases for more complex ones. "Template typedef" // This makes stringmap an alias for std::map template using stringmap = std::map; template<> template<> void cpp_features_test_object_t::test<11>() { stringmap name_counts{ {"alice", 3}, {"bob", 2} }; ensure("type alias", name_counts["bob"]==2); } // Other possibilities: // nullptr // class enums // std::unique_ptr and make_unique // std::shared_ptr and make_shared // lambdas // perfect forwarding // variadic templates // std::thread // std::mutex // thread_local // rvalue reference && // move semantics // std::move // string_view } // namespace tut