Curious behaviour with static_assert and CRTP - c++

While developing a protocol library, I used some preventive static_assert to check whether some different arrays had the same length.
At first, I started using them in all derived classes, but at some moment, I wondered why not to put that protection in base class (the CRTP base class) and save in typewording. I did it, but I then found that it was not possible to do as I expected and I would like to know: 1st/ why? and 2nd/ is it fixable in some way?
The minimal working example of original source code can be this:
#include <stdio.h>
#include <iostream>
#include <array>
template<typename T>
struct skill
{
static constexpr void some_function() { static_assert(T::vars.size() == T::bits.size()); }//This is OK
static_assert(T::vars.size() == T::bits.size());//This one arises an error!!!
private:
skill() = default;
friend T;
};
struct message1 : skill<message1>
{
static constexpr std::array vars{ "id", "len", "source", "destination" };
static constexpr std::array bits{ 8, 10, 12, 12 };
static_assert(vars.size() == bits.size());//This is OK
};
struct message2 : skill<message2>
{
static constexpr std::array vars{ "id", "len", "command" };
static constexpr std::array bits{ 8, 10, 24 };
static_assert(vars.size() == bits.size());//This is OK
};
int main()
{
message1::some_function();
}
Please, note static_assert in struct skill, which is the one arisen the next error: "error: incomplete type 'message1' used in nested name specifier" in g++.
The link to source code in coliru is: https://coliru.stacked-crooked.com/a/24a1916a5055949a
Please, any help with this?

Related

Determine whether a type exists without feature-test macro

I'd like to determine whether a type exists without using a feature-test macro. Here's the idea using a macro:
namespace real
{
struct foo final { static constexpr const char* name = "real::foo"; };
}
#define real_foo_ 314
struct my_foo final { static constexpr const char* name = "my_foo"; };
namespace real
{
#if !real_foo_
using foo = my_foo;
#endif
}
That is, real::foo should to the "real" foo if it exists, otherwise my_foo will be used; subsequent code uses real::foo w/o knowing or caring whether it's the actual version or the replacement.
Achiving the same template meta-programming seems to be the right idea:
#include <type_traits>
namespace real
{
struct bar final { static constexpr const char* name = "real::bar"; };
}
struct my_bar final { static constexpr const char* name = "my_bar"; };
// https://devblogs.microsoft.com/oldnewthing/20190710-00/?p=102678
template<typename, typename = void>
constexpr bool is_type_complete_v = false;
template<typename T>
constexpr bool is_type_complete_v<T, std::void_t<decltype(sizeof(T))>> = true;
namespace real { struct bar; }
namespace real
{
using bar = std::conditional_t<is_type_complete_v<real::bar>, real::bar, my_bar>;
}
The above works as shown:
#include <iostream>
int main()
{
real::foo foo;
std::cout << foo.name << "\n";
real::bar bar;
std::cout << bar.name << "\n";
}
But removing the actual definition (not the forward) of real::bar causes compiler errors:
namespace real
{
//struct bar final { static constexpr const char* name = "real::bar"; };
}
error C2371: 'real::bar': redefinition; different basic types
message : see declaration of 'real::bar'
error C2079: 'bar' uses undefined struct 'real::bar'
Is there a way to make real::bar work like real::foo without relying on a feature-test macro? Note that as with real::foo, the name of real::bar can't be changed (e.g., to real::really_bar).
(Actual use case: C++14/C++17/C++20 library features implemented in C++11; once client code using std::filesystem::path has been written, it shouldn't have to change.)
Compiler complains on your code because you are trying to create two entities with same name. Consider changing
namespace real
{
struct bar final { static constexpr const char* name = "real::bar"; };
}
to something like
namespace real
{
struct absolutely_bar final { static constexpr const char* name = "real::bar"; };
}
...
namespace real { struct absolutely_bar; }
namespace real
{
using bar = std::conditional_t<is_type_complete_v<real::absolutely_bar>, real::absolutely_bar, my_bar>;
}
PS: creating such aliases is usually a bad pattern since it's not obvious.

Inner enum classes of templates not considered constant expressions?

I'm using C++ with GCC's C extension for designated inits / designated array initializers and I've come across a bit of a pickle. If I have the following (contrived) example code,
#include <string>
#include <string_view>
struct STRUCTURE {
enum class ENUM { A, B, C };
static constexpr std::string_view STRINGS[] {
[static_cast<int>(ENUM::A)] = "A",
[static_cast<int>(ENUM::B)] = "B",
[static_cast<int>(ENUM::C)] = "C"
};
constexpr std::string_view operator[](ENUM e) const {
return STRINGS[static_cast<int>(e)];
}
};
Everything works as expected. However, if I now template the structure, i.e.
#include <string>
#include <string_view>
template <typename T>
struct STRUCTURE { /*same structure definition*/ };
I get a compile-time error that the C99 designator initializers I have are not integral constant expressions.
In fact, in some cases with namespaces I get an internal compiler error / internal compiler segfault, leading me to believe this is an internal issue, but I cannot confirm such.
If I move the scoped enum class outside of the template, everything works again. In that manner, I can simply add a level of indirection to my actual code, however I would prefer not doing so.
Here is a godbolt with a macro allowing you to change between the templated and non-templated definition, and a simple main printing out one of the strings.
Even odder: Say I change the type of the internal array to ints. I.e,
struct STRUCTURE {
enum class ENUM { A, B, C };
static constexpr int INTS[] {
[static_cast<int>(ENUM::A)] = 65,
[static_cast<int>(ENUM::B)] = 66,
[static_cast<int>(ENUM::C)] = 67
};
constexpr int operator[](ENUM e) const {
return INTS[static_cast<int>(e)];
}
};
I have the same issue. But now if I assign the values via static casts of the enum as the designators are, i.e,
...
static constexpr int INTS[] {
[static_cast<int>(ENUM::A)] = static_cast<int>(ENUM::A),
[static_cast<int>(ENUM::B)] = static_cast<int>(ENUM::B),
[static_cast<int>(ENUM::C)] = static_cast<int>(ENUM::C)
};
...
This works, template or not. Godbolt for this, too.

How to initialize a template class with a non constant value

I have a template class that needs I need to set the size of using the size of a vector. what is the best way to achieve this? below is a simplified version, I will link to the full class if you need to see it.
template<int maxParams>
class ParameterChangeHandler
{
public:
inline ParameterChangeHandler()
{
//Do Stuff
};
//More Inline Methods that use maxparams and paramBitArray
protected:
unsigned char paramBitArray[(((maxParams)+((8) - 1)) & ~((8) - 1)) / 8]
};
int main()
{
std::vector<int> myVectorOfParameters = { 1,2,3,4,5 };
//This is OK
//ParameterChangeHandler<10> paramChangeHandler;
//This is what I want
ParameterChangeHandler<myVectorOfParameters.size()> paramChangeHandler
}
This is for an audio application that I am building using the Wwise SDK, Here is a link to the actual class that is giving me the issue
AkFXParameterChangeHandler
What you want isn't possible. Here's what you can do. Pass myVectorOfParameters.size() as an argument to the constructor and use std::vector/std::basic_string instead of unsigned char array so you don't need a constant expression for the size.
#include <vector>
#include <cstddef>
class ParameterChangeHandler
{
public:
ParameterChangeHandler(std::size_t const max_size)
: paramBitArray(max_size) // or reserve and push_back as needed
{
// Do Stuff
};
protected:
std::vector<unsigned char> paramBitArray;
};

How to limit char array length in constructor

Using Extended Embedded Cpp. How can I make this result in a compilation error in a release build:
Param okParam("Yeah!"); // this line should be ok
Param nOkParam("REEEEEEEEEEE"); // too big array, not ok. compiler error.
where:
int const c_max = 10;
template<int N>
struct Param
{
char value[c_max];
Param(char const (&p_value)[N])
{
memcpy(value, p_value, sizeof(p_value));
}
};
I don't think you can template the constructor, so the entire struct needs to be templated, right?
I want this to provide a clean compiler error so that the person using this will notice it immediately.
Our version of extended embedded C++ doesn't provide any stl containers, I'm not sure if it's even possible.
I'm looking for some way to make the template result in a good compilation error. Sadly I can't use boost either, since the platform would not support it.
You have basically two solutions: SFINAE (C++98) or static_assert (C++11):
SFINAE
You can provide a constructor for Param only for char arrays less than a given size long. In C++98 this looks a bit ugly, but it works:
#include <cstddef>
template<bool b>
struct enable_if {};
template<>
struct enable_if<true>
{
typedef int type;
};
template<std::size_t MAXSIZE>
struct Param
{
template<std::size_t SIZE>
explicit Param(
char const (&input) [SIZE],
std::size_t = sizeof(typename enable_if<SIZE < MAXSIZE>::type) // SFINAE at work
) { (void) input; }
};
int main()
{
// "hello": char const[6], 6 < 7, OK
Param<7> p1("hello");
// "hello world": char const[12], 12 >= 7, KO
Param<7> p2("hello world"); // ugly error here
}
Live demo
Assert (C++11 only)
Inside the constructor of Param, you can check if the supplied char array is too big and pop a readable error at compilation-time:
#include <cstddef>
#include <type_traits>
template<std::size_t MAXSIZE>
struct Param
{
template<std::size_t SIZE>
explicit Param(char const (&input) [SIZE])
{ static_assert(sizeof(input) < MAXSIZE, "input is too big."); }
};
int main()
{
// "hello": char const[6], 6 < 7, OK
Param<7> p1("hello");
// "hello world": char const[12], 12 >= 7, KO
Param<7> p2("hello world"); // "error: static assertion failed: input is too big."
}
Live demo
Probably the simplest way is to add a static_assert, using one of the old C techniques for compile-time checks if your implementation doesn't have static_assert yet:
#include <cstring>
#if __cplusplus < 201103L
#define static_assert(expr, message) \
int static_assert_(int (&static_assert_failed)[(expr)?1:-1])
#endif
template<int N>
struct Param
{
static const int c_max = 10;
static_assert(N < c_max, "Param string too long");
char value[c_max];
Param(char const (&p_value)[N])
{
std::memcpy(value, p_value, sizeof p_value);
}
};
int main()
{
Param okParam("Yeah!"); // this line should be ok
Param nOkParam("REEEEEEEEEEE"); // too big array, not ok. compiler error.
}

`std::pair` `second` has incomplete type with `unordered_map` tree

I was reviewing some older code of mine and I saw the code using pointers to implement a tree of Variant objects. It is a tree because each Variant can contain an unordered_map of Variant*.
I looked at the code and wondered why isn't it just using values, a std::vector<Variant>, and std::unordered_map<std::string, Variant>, instead of Variant*.
So I went ahead and changed it. It seemed okay except one thing, I got errors:
/usr/local/include/c++/6.1.0/bits/stl_pair.h:153:11: error: 'std::pair<_T1, _T2>::second' has incomplete type
_T2 second; /// #c second is a copy of the second object
^~~~~~ main.cpp:11:8: note: forward declaration of 'struct Variant'
struct Variant
^~~~~~~
So I figured I could trick the compiler into delaying the need to know that type, which didn't work either.
Working Not Working! (MCVE)
I thought this worked earlier but it actually doesn't, I forgot ::type on the using HideMap...
#include <vector>
#include <unordered_map>
#include <iostream>
template<typename K, typename V>
struct HideMap
{
using type = std::unordered_map<K, V>;
};
struct Variant
{
using array_container = std::vector<Variant>;
// Does not work either
using object_container = typename HideMap<std::string, Variant>::type;
// Fails
//using object_container = std::unordered_map<std::string, Variant>;
private:
union Union
{
std::int64_t vint;
array_container varr;
object_container vobj;
// These are required when there are union
// members that need construct/destruct
Union() {}
~Union() {}
};
Union data;
bool weak;
};
int main()
{
Variant v;
std::cout << "Works" << std::endl;
}
So, my question is, why does it work okay for vector and not unordered_map?
If the problem is the inability to use incomplete types, is there a way to delay the instantiation of the unordered_map? I really don't want every object property to be a separate new allocation.
This uses placement new to defer the initialization of the Union to the constructor where Variant is a complete type. You need to reinterpret_cast everywhere you need to use the Union. I made an effort to not have any strict-alignment violations.
#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <vector>
struct Variant {
Variant();
~Variant();
private:
std::aligned_union<0, std::vector<Variant>,
std::unordered_map<std::string, void *>,
std::int64_t>::type data;
};
namespace Variant_detail {
using array_container = std::vector<Variant>;
using object_container = std::unordered_map<std::string, Variant>;
union Union {
std::int64_t vint;
array_container varr;
object_container vobj;
// These are required when there are union
// members that need construct/destruct
Union() {}
~Union() {}
};
}
Variant::Variant() {
//make sure that std::unordered_map<std::string, Variant> is not too large
static_assert(sizeof(std::unordered_map<std::string, Variant>) <=
sizeof data, "Variant map too big");
static_assert(alignof(std::unordered_map<std::string, Variant>) <=
alignof(decltype(data)), "Variant map has too high alignment");
auto &my_union = *new (&data) Variant_detail::Union;
my_union.vint = 42;
}
Variant::~Variant() {
reinterpret_cast<Variant_detail::Union &>(data).~Union();
}
int main() {
Variant v;
std::cout << "Works" << std::endl;
}