387 lines
10 KiB
C++
387 lines
10 KiB
C++
/**
|
|
* @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> 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<S32> 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<S32> 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<S32> 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<S32> 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<T> an alias for std::map<std::string, T>
|
|
template<typename T>
|
|
using stringmap = std::map<std::string, T>;
|
|
|
|
template<> template<>
|
|
void cpp_features_test_object_t::test<11>()
|
|
{
|
|
stringmap<S32> 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
|