sigs has been migrated to using C++17 from C++14. Didn’t take much doing other than some CMake configuration tweakings, and to update the Google Test framework to version 1.8.1.
1. CMake configuration changes
Setting the required C++ standard to C++17 was done with:
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
And was tested to work with Clang 6 + 7, GCC 7 + 8, and VS 2017.
2. C++17 features
The fun part was finding new features to put into action in the project.
2.1. [[nodiscard]]
In certain cases it is important that the returned value is not discarded, which is exactly what [[nodiscard]]
achieves. For example, in the following a warning will be shown if the return value of Signal::interface()
isn’t used:
[[nodiscard]] inline std::unique_ptr<Interface> interface() noexcept
{
return std::make_unique<Interface>(this);
}
2.2. Initializer statements
Having the means to include initialization in if
and switch
statements is a very nice, new feature that helps scoping variables for intent. If an initialization is made before the statement then it will be live in the parent scope of that statement, a scope that might span for more than what was intended.
There were no places in sigs where a scope of an initializer to an if
or switch
statement spanned more than was necessary, but still, there were four places to put it into effect. An example:
if (auto *sig = entry.signal(); sig) {
(*sig)(retFunc, std::forward<Args>(args)...);
}
2.3. std::optional
The concept of an optional value is a nice abstraction that enables one to not use any value, from the return value space, to act as an indicator of null or an error. For instance, if the return type of a function is int
then an error might be signaled by using -1
, but that also means that the value cannot be used as a valid value. Instead, std::optional<T>
can be seen as a bool
for indicating an error and the encapsulated value T
if defined.
An example usage from the project:
template <typename Ret, typename... Args>
class Signal<Ret(Args...)> final {
public:
// ..
void disconnect(std::optional<Connection> conn) noexcept
{
// ..
}
// ..
This solution is clearer and better than the previous one:
void disconnect(Connection conn = nullptr)
2.4. std::is_void_v
std::is_void
was introduced already in C++11 and std::is_void_v
is basically just a helper variable template:
template <typename T>
inline constexpr bool is_void_v = is_void<T>::value;
In sigs, I ended up replacing the local isVoidReturn()
with direct usage due to the shortness of the statement:
static_assert(!std::is_void_v<ReturnType>, "Must have non-void return type!");
2.5. noexcept
Every function in C++ is either potentially throwing or non-throwing. noexcept
is interesting because it marks a function as non-throwing, and informs that exceptions aren’t being used intentionally. If an exception is thrown anyway, std::terminate
is invoked.
noexcept
was added in C++11 but I didn’t put it to use until now. It made it possible for the compiler to optimize further and reduced the size of the binaries somewhat. Can’t say no to that. I found 34 places to add it, like:
Signal() noexcept = default;