I’m trying to use static_assert to force something to fail. If you try to instantiate a specific templated function in a specific way I want to generate a complier error. I could make it work, but it was really ugly. Is there an easier way to do this?
This was my first attempt. This did not work at all. It always generates an error, even if no one tries to use this function.
template< class T >
void marshal(std::string name, T *value)
{
static_assert(false, "You cannot marshal a pointer.");
}
Here’s my second attempt. It actually works. If you don’t call this, you get no error. If you do call this, you get a very readable error message that points to this line and points to the code that tried to instantiate it.
template< class T >
void marshal(std::string name, T *value)
{
static_assert(std::is_pod<T>::value && !std::is_pod<T>::value, "You cannot marshal a pointer.");
}
The problem is that this code is ugly at best. It looks like a hack. I’m afraid the next time I change the optimization level, upgrade my compiler, sneeze, etc, the compiler will realize that this second case is the same as the first, and they will both stop working.
Is there a better way to do what I’m trying to do?
Here’s some context. I want to have several different versions of marshal() which work for different input types. I want one version that uses a template as the default case. I want another one that specifically disallows any pointers except char *.
void marshal(std::string name, std::string)
{
std::cout<<name<<" is a std::string type."<<std::endl;
}
void marshal(std::string name, char *string)
{
marshal(name, std::string(string));
}
void marshal(std::string name, char const *string)
{
marshal(name, std::string(string));
}
template< class T >
void marshal(std::string name, T value)
{
typedef typename std::enable_if<std::is_pod<T>::value>::type OnlyAllowPOD;
std::cout<<name<<" is a POD type."<<std::endl;
}
template< class T >
void marshal(std::string name, T *value)
{
static_assert(false, "You cannot marshal a pointer.");
}
int main (int argc, char **argv)
{
marshal("should be pod", argc);
marshal("should fail to compile", argv);
marshal("should fail to compile", &argc);
marshal("should be std::string", argv[0]);
}
Prior to a DR against c++23, there was no way to do this. You might be able to make it work on your compiler, but the resulting program is ill formed no diagnostic required.
Use =delete
.
template< class T >
void marshal(std::string name, T *value) = delete;
After c++23's DR 2518 was resolved, the wording was tweaked:
In a static_assert-declaration, the constant-expression is contextually converted to bool and the converted expression shall be a constant expression (7.7 [expr.const]). If the value of the expression when so converted is true or the expression is evaluated in the context of a template definition, the declaration has no effect. Otherwise, the static_assert-declaration fails, the program is ill-formed, and the resulting diagnostic message (4.1 [intro.compliance]) should include the text of the string-literal, if one is supplied.
the bolded text means that a static_assert(false, "whatever")
in a template definition but not instantiation doesn't cause your program to be in violation of the C++ standard. In addition, clauses that require all templates to have a valid instantiation where relaxed to exclude failures caused by static_assert
s.
The result is this example from the amended standard:
template <class T> void f(T t) { if constexpr (sizeof(T) == sizeof(int)) { use(t); } else { static_assert(false, "must be int-sized"); } }
being perfectly a-ok, with the failure occurring if you call f
with a non-int
sized T
.
Defect reports are considered retroactive changes - so a conforming c++23 compiler must implement the defect report, and can do so without having to say they are using a 'newer' version of the standard. Not all c++23 compilers will do so, both because most C++ compilers tend to be incomplete implementations of a standard, and because they may have been written before c++23 was redefined.