4

Let me set the scene..

You can open files in a specific mode like this:

#include <fstream>

int main(){

    std::fstream myfile;
    myfile.open ("filename", std::ios::app);

    return 0;
}

that second parameter is an enumerated type-
which is why you will get a compiler error attempting this:

#include <fstream>

int main(){

    std::fstream myfile;
    myfile.open ("filename", std::ios::lksdjflskdjflksff);

    return 0;
}

In this example, the class doesn't have to account for the second parameter being incorrect, and the programmer never has to worry about passing in a nonsensical value.

Question: Is there a way to write functions that must take a particular type AND a particular value?

Let's say I wanted to re-implement a File Handling class similar to the one above. The difference is I'm making the second parameter a char instead of an enumerated type.
How could I get something like this to work:

#include "MyFileHandler.h"

int main(){

    MyFileHandler myfile1;

    myfile.open ("filename", 'a'); //GOOD: a stands for append
    myfile.open ("filename", 't'); //GOOD: t stands for truncate
    myfile.open ("filename", 'x'); //COMPILER ERROR: openmode can not be the value 'x'

    return 0;
}

Going beyond this, can I get the compiler to test the validity of argument values through functional means? Example:

void IOnlyAcceptPrimeNumbers(const int & primeNumber);
int function(void);

int main(){

    IOnlyAcceptPrimeNumbers(3);       //GOOD: 3 is prime
    IOnlyAcceptPrimeNumbers(7);       //GOOD: 7 is prime
    IOnlyAcceptPrimeNumbers(10);      //COMPILER ERROR: 10 is not prime
    IOnlyAcceptPrimeNumbers(10+1);    //GOOD: 11 is prime
    IOnlyAcceptPrimeNumbers(1+1+1+1); //COMPILER ERROR: 4 is not prime
    IOnlyAcceptPrimeNumbers(function()); //GOOD: can this somehow be done?


    return 0;
}
void IOnlyAcceptPrimeNumbers(const int & primeNumber){return;}
int function(void){return 7;}

I believe i've made it clear what I want to do and why I find it important.
Any solutions out there?

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • `open` would still have to check its parameters, because you can trick it with an invalid mode `myfile.open("name", std::ios::openmode(42));`. – Bo Persson Feb 18 '12 at 14:40

5 Answers5

7

If you want compile-time checked values, you could write templates rather than function arguments:

template <char> void foo(std::string const &);      // no implementation

template <> void foo<'a'>(std::string const & s) { /* ... */ } 
template <> void foo<'b'>(std::string const & s) { /* ... */ }

Usage:

foo<'a'>("hello world");   // OK
foo<'z'>("dlrow olleh");   // Linker error, `foo<'z'>` not defined.

If you want an actual compiler error rather than just a linker error, you could add a static_assert(false) into the primary template.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Just one little point: within your specialized functions you should call a non-template function that does the implementation. By such you have a single implementation without the code duplication, only for "allowed" values. – valdo Feb 18 '12 at 15:05
  • Linker errors are ominous, consider instead using `static_assert` in the general version instead of omitting an implementation. – deft_code Feb 21 '12 at 18:54
1

No, if you specify that your function will take a char, it will take any char.

The "resolution" used by the compiler for checking passed arguments is the type rather than a set of possible values.

In other words, you need to use enumerations for this, or move the checking to runtime, or do something horrid like:

static void processAorT (char typ, char *fileName) { ... }
void processA (char *fileName) { processAorT ('a', fileName); }
void processT (char *fileName) { processAorT ('t', fileName); |

(not something I would advise, by the way).


Having said that, I'm not sure what you're proposing is a good idea anyway.

The compiler may be able to detect invalid constants, but won't be very successful if the parameter passed into IOnlyAcceptPrimeNumbers has come from a variable or, worse, input by a user.

The API is a contract between caller and function and, if the rules of that contract are not followed, you're free to do whatever you want, though hopefully you'd document it.

In other words, that function should begin:

void IOnlyAcceptPrimeNumbers (int num) {
    if (!isPrime (num)) return;
    // do something with a prime number.
}

(or the equivalent for your function that accepts a and t but not x). Doing nothing when passed invalid parameters is a reasonable strategy, as is returning an error or throwing an exception (though no doubt some would argue with this).

How you handle it is up to you, but it needs to be handled at runtime simply because the compiler doesn't have all the information.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Interesting extension, but the question was defined **explicitly** for compile-time checking. Actually this **may** be done in some circumstances using templates – valdo Feb 18 '12 at 15:01
  • @valdo, templates have the same shortcoming as my "horrid" suggestion, repeated code or having to hide a real (shared) function from the linker. Definitely _not_ something I'd suggest. – paxdiablo Feb 18 '12 at 15:06
  • no code is actually repeated. You have a single non-template implementation function, which is wrapped by a template function, specialized only for allowed arguments. – valdo Feb 18 '12 at 15:36
  • @valdo, hence the "or" in my comment :-) It's equivalent in that case to the `processAorT` code in my answer. In any case, it still won't work when the character is not _known_ at compile time - that's a fundamental limitation that you cannot get around at compile time. – paxdiablo Feb 18 '12 at 15:56
0

You can only check value validity at runtime. Best you can do is use assert to stop programm execution if precondition is violated.

Joel Falcou
  • 6,247
  • 1
  • 17
  • 34
  • I'd use a different word than assert so as to not confuse the issue with `assert()`, which is typically ignored in release code. – paxdiablo Feb 18 '12 at 14:32
  • 1
    its the general term. I agree on the release problem but as it's precondition violation, it has to be asserted not throw any eeption (see abrahams paper on the subject) – Joel Falcou Feb 18 '12 at 15:55
0

No. If you want to restrict the accepted arguments you need to use enums or accept an object that inherits from a specific interface (depends how sophisticated you want to make it). Enums is the common way to address this issue.

The example about the IOnlyAcceptPrimeNumbers is not well designed. If you want to achieve something similar it would be better to provide a class method that is something such as bool setNumber(int number) that will return false if the number is not prime. If you want to do it in the costructor the real alternative is to throw an exception (that is not really nice to do).

The concept is that you can not simply rely that the user will pass you only elements from a (correct) subset of the values that the parameter type allows.

Riccardo Tramma
  • 583
  • 3
  • 8
0

While more restrictive than your requirements (this limits the values a particular type can hold), you can always try something like:

// Vowel.h
#ifndef VOWEL_H_
#define VOWEL_H_

class Vowel
{
public:
    static const Vowel A;
    static const Vowel E;
    static const Vowel I;
    static const Vowel O;
    static const Vowel U;

    char get() const { return value; }

private:
    explicit Vowel(char c);

    char value;
};

#endif  /* VOWEL_H_ */


// Vowel.cpp
#include "Vowel.h"

Vowel::Vowel(char c) : value(c) {}

const Vowel Vowel::A('A');
const Vowel Vowel::E('E');
const Vowel Vowel::I('I');
const Vowel Vowel::O('O');
const Vowel Vowel::U('U');

Since the char constructor is private, only Vowel itself can construct objects from chars. All other uses are done by copy construction or copy assignment.

(I think I originally learned this technique from Scott Meyers; thank him / blame me.)

Nevin
  • 4,595
  • 18
  • 24